From 18cc2c2fd4f29ead5ad0a224ccd595bee564b5fd Mon Sep 17 00:00:00 2001 From: glibsonoran <31249593+glibsonoran@users.noreply.github.com> Date: Sat, 23 Nov 2024 08:47:04 -0700 Subject: [PATCH] Version 1.21.20 --- __init__.py | 2 +- api_requests.py | 1496 +++++++++++++++++++++++++++-------------------- fetch_models.py | 3 +- help.json | 5 +- style_prompt.py | 44 +- update.json | 1 + 6 files changed, 895 insertions(+), 656 deletions(-) diff --git a/__init__.py b/__init__.py index f1a3184..33a049c 100644 --- a/__init__.py +++ b/__init__.py @@ -17,7 +17,7 @@ if jmanager.on_startup(False): else: jmanager.log_events("config.json was not updated") -__version__ ="1.21.19" +__version__ ="1.21.20" print('Plush - Version:', __version__) diff --git a/api_requests.py b/api_requests.py index 0988735..5e51b41 100644 --- a/api_requests.py +++ b/api_requests.py @@ -1,18 +1,28 @@ +# Standard library from abc import ABC, abstractmethod -import torch import time +import json import re -import requests +from enum import Enum +from typing import Callable, Any, Optional, Type, Union, List, Tuple from urllib.parse import urlparse, urlunparse + +# Third-party libraries +import torch +import requests import openai import anthropic + +# Local imports from .mng_json import json_manager, TroubleSgltn from .fetch_models import RequestMode -from .utils import ImageUtils +from .utils import ImageUtils + class ImportedSgltn: """ - This class is temporary to prevent circular imports + This class is temporary to prevent circular imports between style_prompt + and api_requests modules. """ _instance = None @@ -27,35 +37,345 @@ class ImportedSgltn: self._initialized = True self._cfig = None self._dalle = None - self._request_mode = None self.get_imports() def get_imports(self): + """Import and initialize singleton instances from style_prompt""" # Guard against re-importing if already done if self._cfig is None or self._dalle is None: from .style_prompt import cFigSingleton, DalleImage self._cfig = cFigSingleton self._dalle = DalleImage - self._request_mode = RequestMode @property def cfig(self): - + """Returns the cFigSingleton instance""" if self._cfig is None: self.get_imports() return self._cfig() @property def dalle(self): - + """Returns the DALLE instance""" if self._dalle is None: self.get_imports() return self._dalle() +class RetryConfig: + """Configuration for retry behavior""" + def __init__( + self, + max_retries: int = 3, + base_delay: float = 1.0, + max_delay: float = 10.0, + exponential_base: float = 2.0, + retryable_exceptions: Optional[List[Type[Exception]]] = None, + retryable_http_status_codes: Optional[List[int]] = None + ): + self.max_retries = max_retries + self.base_delay = base_delay + self.max_delay = max_delay + self.exponential_base = exponential_base + self.retryable_exceptions = retryable_exceptions + self.retryable_http_status_codes = retryable_http_status_codes or [ + 408, # Request Timeout + 429, # Too Many Requests + 500, # Internal Server Error + 502, # Bad Gateway + 503, # Service Unavailable + 504 # Gateway Timeout + ] + + +class ErrorParser: + """Extracts standardized error information from various API responses""" + + @staticmethod + def get_error_code(response: Any) -> Optional[int]: + """ + Extracts error code from various response formats. + Returns error code if found, None otherwise. + """ + # Handle HTTP Response objects + if isinstance(response, requests.Response): + return response.status_code + + # OpenAI-style errors (and compatible services like OpenRouter) + if hasattr(response, 'error'): + error = response.error + if isinstance(error, dict): + # Direct error code + if 'code' in error and isinstance(error['code'], int): + return error['code'] + + if 'status' in error and isinstance(error['status'], int): + return error['status'] + + if 'status_code' in error and isinstance(error['status_code'], int): + return error['status_code'] + + # Nested in metadata (like OpenRouter/Google) + metadata = error.get('metadata', {}) + if metadata and isinstance(metadata.get('raw'), str): + try: + raw_error = json.loads(metadata['raw']) + code = raw_error.get('error', {}).get('code') + if isinstance(code, int): + return code + except (json.JSONDecodeError, AttributeError): + pass + + # Anthropic-style responses + if hasattr(response, 'status_code'): + return response.status_code + + # Handle raw JSON responses (some services return direct JSON) + if isinstance(response, dict): + # Try common error code paths + paths = [ + ['error', 'code'], + ['error', 'status_code'], + ['error', 'status'], + ['code'], + ['status_code'], + ['status'] + ] + for path in paths: + value = response + for key in path: + if isinstance(value, dict) and key in value: + value = value[key] + else: + value = None + break + if isinstance(value, int): + return value + + return None + +class RetryHandler: + """Handles retry logic for API calls""" + def __init__(self, config: RetryConfig, logger: Any): + self.config = config + self.logger = logger + self.error_parser = ErrorParser() + + def calculate_delay(self, attempt: int) -> float: + """Calculate delay with exponential backoff""" + delay = min( + self.config.base_delay * (self.config.exponential_base ** attempt), + self.config.max_delay + ) + return delay + + def should_retry(self, response: Any) -> bool: + """Determine if the response is retryable""" + error_code = self.error_parser.get_error_code(response) + + if error_code: + # Check if it's a retryable code + return error_code in self.config.retryable_http_status_codes + + # Handle standard exceptions + if isinstance(response, Exception) and self.config.retryable_exceptions: + return any(isinstance(response, exc) for exc in self.config.retryable_exceptions) + + return False + + def execute_with_retry(self, func: Callable, *args, **kwargs) -> Any: + """Execute function with retry logic""" + last_exception = None + last_error_info = None # Track the last error information + self.logger.log_events(f"Maximum tries set to: {self.config.max_retries}", + is_trouble=True) + + for attempt in range(self.config.max_retries): + try: + response = func(*args, **kwargs) + + # For HTTP responses + if isinstance(response, requests.Response): + try: + response_json = response.json() + if 'error' in response_json: + error_code = self.error_parser.get_error_code(response_json) + if error_code in self.config.retryable_http_status_codes: + last_error_info = response_json['error'] # Store error info + delay = self.calculate_delay(attempt) + + self.logger.log_events( + f"Retryable error detected in response content ({error_code}), " + f"retrying in {delay:.2f} seconds...", + TroubleSgltn.Severity.WARNING, + True + ) + time.sleep(delay) + continue + except ValueError: + pass + + # Then check status codes + if 200 <= response.status_code < 300: + return response + elif self.should_retry(response): + last_error_info = {'status': response.status_code, 'text': response.text} + delay = self.calculate_delay(attempt) + self.logger.log_events( + f"Rate limit or server error {response.status_code}, " + f"retrying in {delay:.2f} seconds...", + TroubleSgltn.Severity.WARNING, + True + ) + time.sleep(delay) + continue + else: + return response + + # For OpenAI/API responses with embedded errors + error_code = self.error_parser.get_error_code(response) + if error_code and error_code in self.config.retryable_http_status_codes: + last_error_info = response.error if hasattr(response, 'error') else str(response) + delay = self.calculate_delay(attempt) + self.logger.log_events( + f"Rate limit or error detected in API response ({error_code}), " + f"retrying in {delay:.2f} seconds...", + TroubleSgltn.Severity.WARNING, + True + ) + time.sleep(delay) + continue + + return response + + except Exception as e: + last_exception = e + last_error_info = str(e) # Store exception info + + if not self.should_retry(e): + self.logger.log_events( + f"Non-retryable error occurred: {str(e)}", + TroubleSgltn.Severity.ERROR, + True + ) + raise + + delay = self.calculate_delay(attempt) + self.logger.log_events( + f"Attempt {attempt + 1}/{self.config.max_retries} failed. " + f"Retrying in {delay:.2f} seconds. Error: {str(e)}", + TroubleSgltn.Severity.WARNING, + True + ) + time.sleep(delay) + + # Create a meaningful exception with the last error information + error_message = f"Maximum retry attempts ({self.config.max_retries}) exceeded. " + if last_error_info: + error_message += f"Last error: {last_error_info}" + + # Raise the original exception if we have one, otherwise raise a RuntimeError + if last_exception: + raise last_exception + raise RuntimeError(error_message) + +class RetryConfigFactory: + """Factory for creating retry configurations based on request type""" + + @staticmethod + def create_config(request_type: RequestMode) -> RetryConfig: + web_exceptions = [ + requests.exceptions.Timeout, + requests.exceptions.ConnectionError, + requests.exceptions.RequestException, + ConnectionError, + TimeoutError + ] + + api_exceptions = [ + openai.APIConnectionError, + openai.RateLimitError, + openai.APIStatusError + ] + + anthropic_exceptions = [ + anthropic.APIConnectionError, + anthropic.RateLimitError, + anthropic.APIStatusError, + anthropic.APIError + ] + + configs = { + RequestMode.OPENAI: RetryConfig( + max_retries=3, + base_delay=1.0, + max_delay=10.0, + retryable_exceptions=api_exceptions + ), + RequestMode.CLAUDE: RetryConfig( + max_retries=2, + base_delay=2.0, + max_delay=8.0, + retryable_exceptions=anthropic_exceptions + ), + RequestMode.OPENSOURCE: RetryConfig( + max_retries=3, + base_delay=1.0, + max_delay=8.0, + retryable_exceptions=web_exceptions, + retryable_http_status_codes=[408, 429, 500, 502, 503, 504] + ), + RequestMode.OSSIMPLE: RetryConfig( + max_retries=3, + base_delay=1.0, + max_delay=8.0, + retryable_exceptions=web_exceptions, + retryable_http_status_codes=[408, 429, 500, 502, 503, 504] + ), + RequestMode.LMSTUDIO: RetryConfig( + max_retries=2, + base_delay=0.5, + max_delay=4.0, + retryable_exceptions=web_exceptions, + retryable_http_status_codes=[408, 429, 500, 502, 503, 504] + ), + RequestMode.GROQ: RetryConfig( + max_retries=3, + base_delay=1.0, + max_delay=6.0, + retryable_exceptions=api_exceptions + ), + RequestMode.OOBABOOGA: RetryConfig( + max_retries=2, + base_delay=1.0, + max_delay=6.0, + retryable_exceptions=web_exceptions, + retryable_http_status_codes=[408, 429, 500, 502, 503, 504] + ), + # DALL-E specific configuration + RequestMode.DALLE: RetryConfig( + max_retries=3, + base_delay=2.0, + max_delay=15.0, + retryable_http_status_codes=[400,429], + retryable_exceptions=[ + openai.APIConnectionError, + openai.RateLimitError, + openai.APIStatusError + ] + ) + } + return configs.get(request_type, RetryConfig()) + -#Begin Strategy Pattern class Request(ABC): + """Abstract base class for all request types""" + + class RequestType(Enum): + COMPLETION = "completion" + POST = "post" + IMAGE = "image" + ANTHROPIC = "claude" def __init__(self): self.imps = ImportedSgltn() @@ -64,722 +384,637 @@ class Request(ABC): self.mode = RequestMode self.dalle = self.imps.dalle self.j_mngr = json_manager() + + # Initialize retry configuration and handler + retry_config = RetryConfigFactory.create_config(self.cFig.lm_request_mode) + self.retry_handler = RetryHandler(retry_config, self.j_mngr) + + def _initialize_retry_handler(self, **kwargs): + """Initialize retry handler with optional override from kwargs""" + # Get base configuration + retry_config = RetryConfigFactory.create_config(self.cFig.lm_request_mode) + + # Override max_retries if provided in kwargs otherwise use default + if 'tries' in kwargs and kwargs['tries']: + tries = kwargs['tries'] + if isinstance(tries, str) and tries != "default": + retry_config.max_retries = int(tries) + + self.retry_handler = RetryHandler(retry_config, self.j_mngr) + + def _make_request(self, request_type: RequestType, *args) -> Any: + """Unified request method handling different request types""" + if request_type == self.RequestType.COMPLETION: + client, params = args + return client.chat.completions.create(**params) + + elif request_type == self.RequestType.ANTHROPIC: + client, params = args + return client.messages.create(**params) + + elif request_type == self.RequestType.POST: + url, headers, params = args + return requests.post(url, headers=headers, json=params, timeout=(12, 120)) + + elif request_type == self.RequestType.IMAGE: + client, params = args + return client.images.generate(**params) + + else: + raise ValueError(f"Unsupported request type: {request_type}") @abstractmethod - def request_completion(self, **kwargs) -> None: + def request_completion(self, **kwargs) -> Any: pass -class oai_object_request(Request): #Concrete class + def _process_image(self, image: Optional[Union[str, torch.Tensor]]) -> Optional[str]: + """Common image processing logic""" + if not image: + return None - - def request_completion(self, **kwargs): - + if isinstance(image, torch.Tensor): + image = self.dalle.tensor_to_base64(image) + + if not isinstance(image, str): + self.j_mngr.log_events( + "Image file is invalid. Image will be disregarded in the generated output.", + TroubleSgltn.Severity.WARNING, + True + ) + return None + + return image + + def _log_completion_metrics(self, response: Any, response_type: str = "standard"): + """Common logging for completion metrics""" + try: + if response_type == "standard": + if getattr(response, 'model', None): + self.j_mngr.log_events( + f"Using LLM: {response.model}", + is_trouble=True + ) + if getattr(response, 'usage', None): + self.j_mngr.log_events( + f"Tokens Used: {response.usage}", + TroubleSgltn.Severity.INFO, + True + ) + elif response_type == "json": + if response.get('model'): + self.j_mngr.log_events( + f"Using LLM: {response['model']}", + is_trouble=True + ) + if response.get('usage'): + self.j_mngr.log_events( + f"Tokens Used: {response['usage']}", + TroubleSgltn.Severity.INFO, + True + ) + except Exception as e: + self.j_mngr.log_events( + f"Unable to report completion metrics: {e}", + TroubleSgltn.Severity.INFO, + True + ) + +class oai_object_request(Request): + """Concrete class for OpenAI API object-based requests""" + + # def _make_completion_request(self, client, params): + # """Wrapped completion request for retry handling""" + # return client.chat.completions.create(**params) + + def _get_client(self) -> Optional[Any]: + """Get appropriate client based on request type""" + request_type = self.cFig.lm_request_mode + client = None + error_message = None + + if request_type in [self.mode.OPENSOURCE, self.mode.OLLAMA]: + if self.cFig.lm_url: + self.j_mngr.log_events( + "Setting client to OpenAI Open Source LLM object", + is_trouble=True + ) + client = self.cFig.lm_client + else: + error_message = "Open Source api object is not ready for use, no URL provided." + + elif request_type == self.mode.GROQ: + if self.cFig.lm_url: + self.j_mngr.log_events( + "Setting client to OpenAI Groq LLM object", + is_trouble=True + ) + client = self.cFig.lm_client + else: + error_message = "Groq OpenAI api object is not ready for use, no URL provided." + + elif request_type == self.mode.OPENAI: + if self.cFig.key: + self.j_mngr.log_events( + "Setting client to OpenAI ChatGPT object", + is_trouble=True + ) + client = self.cFig.openaiClient + else: + error_message = "Invalid or missing OpenAI API key. Keys must be stored in an environment variable." + + if error_message: + self.j_mngr.log_events( + error_message, + TroubleSgltn.Severity.WARNING, + True + ) + + return client + + def request_completion(self, **kwargs) -> str: + """Execute completion request with retry handling""" GPTmodel = kwargs.get('model') creative_latitude = kwargs.get('creative_latitude', 0.7) - tokens = kwargs.get('tokens',500) + tokens = kwargs.get('tokens', 500) prompt = kwargs.get('prompt', "") instruction = kwargs.get('instruction', "") - file = kwargs.get('file',"") + #file = kwargs.get('file', "").strip() image = kwargs.get('image', None) example_list = kwargs.get('example_list', []) add_params = kwargs.get('add_params', None) - request_type = self.cFig.lm_request_mode - response = None CGPT_response = "" - file += file.strip() - client = None - - if request_type == self.mode.OPENSOURCE or request_type == self.mode.OLLAMA: - if self.cFig.lm_url: - self.j_mngr.log_events("Setting client to OpenAI Open Source LLM object", - is_trouble=True) - - client = self.cFig.lm_client - - #Force the correct url path - corrected_url = self.utils.validate_and_correct_url(self.cFig.lm_url,'/v1') - client.base_url = corrected_url - else: - self.j_mngr.log_events("Open Source api object is not ready for use, no URL provided. Aborting", - TroubleSgltn.Severity.WARNING, - is_trouble=True) - return CGPT_response - - if request_type == self.mode.GROQ: - if self.cFig.lm_url: - self.j_mngr.log_events("Setting client to OpenAI Groq LLM object", - is_trouble=True) - client = self.cFig.lm_client - else: - self.j_mngr.log_events("Groq OpenAI api object is not ready for use, no URL provided. Aborting", - TroubleSgltn.Severity.WARNING, - is_trouble=True) - - if request_type == self.mode.OPENAI: - if self.cFig.key: - self.j_mngr.log_events("Setting client to OpenAI ChatGPT object", - is_trouble=True) - client = self.cFig.openaiClient - else: - CGPT_response = "Invalid or missing OpenAI API key. Keys must be stored in an environment variable (see: ReadMe). ChatGPT request aborted" - self.j_mngr.log_events("Invalid or missing OpenAI API key. Keys must be stored in an environment variable (see: ReadMe). ChatGPT request aborted", - TroubleSgltn.Severity.WARNING, - is_trouble=True) - return CGPT_response - - + client = self._get_client() + self._initialize_retry_handler(**kwargs) if not client: - if request_type == self.mode.OPENAI: - self.j_mngr.log_events("Invalid or missing OpenAI API key. Keys must be stored in an environment variable (see: ReadMe). ChatGPT request aborted", - TroubleSgltn.Severity.ERROR, - True) - CGPT_response = "Invalid or missing OpenAI API key. Keys must be stored in an environment variable (see: ReadMe). ChatGPT request aborted" - - else: - self.j_mngr.log_events("LLM client not set. Make sure local Server is running if using a local LLM front-end", - TroubleSgltn.Severity.ERROR, - True) - CGPT_response = "Unable to process request, make sure local server is running" - return CGPT_response + return "Unable to process request, client initialization failed" - #there's an image - if image: - # Use the user's selected vision model if it's what was chosen, - #otherwise use the last vision model in the list - #If the user is using a local LLM they're on their own to make - #the right model selection for handling an image + # Process image if present + image = self._process_image(image) - if isinstance(image, torch.Tensor): #just to be sure - image = self.dalle.tensor_to_base64(image) - - if not isinstance(image,str): - image = None - self.j_mngr.log_events("Image file is invalid. Image will be disregarded in the generated output.", - TroubleSgltn.Severity.WARNING, - True) - - messages = [] - - #Use basic data structure if there is no image + # Build messages based on presence of image if not image: messages = self.utils.build_data_basic(prompt, example_list, instruction) else: messages = self.utils.build_data_multi(prompt, instruction, example_list, image) - - if not prompt and not image and not instruction and not example_list: - # User has provided no prompt, file or image - response = "Photograph of an stained empty box with 'NOTHING' printed on its side in bold letters, small flying moths, dingy, gloomy, dim light rundown warehouse" - self.j_mngr.log_events("No instruction and no prompt were provided, the node was only able to provide a 'Box of Nothing'", - TroubleSgltn.Severity.WARNING, - True) - return response + # Handle empty input case + if not any([prompt, image, instruction, example_list]): + return "Photograph of a stained empty box with 'NOTHING' printed on its side in bold letters" + + # Prepare request parameters params = { - "model": GPTmodel, - "messages": messages, - "temperature": creative_latitude, - "max_tokens": tokens - } - - # Add the parameter if it exists - if add_params: - add_keys =['param','value'] - self.j_mngr.append_params(params, add_params, add_keys) - - try: - response = client.chat.completions.create(**params) - - except openai.APIConnectionError as e: # from httpx. - self.j_mngr.log_events(f"Server connection error: {e.__cause__}", - TroubleSgltn.Severity.ERROR, - True) - if request_type == self.mode.OPENSOURCE: - self.j_mngr.log_events(f"Local server is not responding to the URL: {self.cFig.lm_url}. Make sure your LLM Manager/Front-end app is running and its local server is live.", - TroubleSgltn.Severity.WARNING, - True) - except openai.RateLimitError as e: - error_message = e.body.get('message', "No error message provided") if isinstance(e.body, dict) else str(e.body or "No error message provided") - self.j_mngr.log_events(f"Server STATUS error {e.status_code}: {error_message}.", - TroubleSgltn.Severity.ERROR, - True) - except openai.APIStatusError as e: - error_message = e.body.get('message', "No error message provided") if isinstance(e.body, dict) else str(e.body or "No error message provided") - self.j_mngr.log_events(f"Server STATUS error {e.status_code}: {error_message}.", - TroubleSgltn.Severity.ERROR, - True) - except Exception as e: - self.j_mngr.log_events(f"An unexpected server error occurred.: {e}", - TroubleSgltn.Severity.ERROR, - True) - - - if response and response.choices and 'error' not in response: - rpt_model = "" - rpt_usage = "" - try: - rpt_model = response.model - rpt_usage = response.usage - except Exception as e: - self.j_mngr.log_events(f"Unable to report some completion information, error: {e}", - TroubleSgltn.Severity.INFO, - True) - if rpt_model: - self.j_mngr.log_events(f"Using LLM: {rpt_model}", - is_trouble=True) - if rpt_usage: - self.j_mngr.log_events(f"Tokens Used: {rpt_usage}", - TroubleSgltn.Severity.INFO, - True) - CGPT_response = response.choices[0].message.content - CGPT_response = self.utils.clean_response_text(CGPT_response) - else: - err_mess = getattr(response, 'error', "Error message missing") - - CGPT_response = "Server was unable to process the request" - self.j_mngr.log_events(f"Server was unable to process this request. Error: {err_mess}", - TroubleSgltn.Severity.ERROR, - True) - return CGPT_response - -class oai_web_request(Request): - - - def request_completion(self, **kwargs): - - """ - Uses the incoming arguments to construct a JSON that contains the request for an LLM response. - Accesses an LLM via an http POST. - Sends the request via http. Handles the OpenAI return object and extacts the model and the response from it. - - Args: - GPTmodel (str): The ChatGPT model to use in processing the request. Alternately this serves as a flag that the function will processing open source LLM data (GPTmodel = "LLM") - creative_latitude (float): A number setting the 'temperature' of the LLM - tokens (int): A number indicating the max number of tokens used to process the request and response - url (str): The url for the server the information is being sent to - request_:type (Enum): Specifies whether the function will be using a ChatGPT configured api object or an third party/url configured api object. - prompt (str): The users' request to action by the LLM - instruction (str): Text describing the conditions and specific requirements of the return value - image (b64 JSON/str): An image to be evaluated by the LLM in the context of the instruction - - Return: - A string consisting of the LLM's response to the instruction and prompt in the context of any image and/or file - """ - GPTmodel = kwargs.get('model', "") - creative_latitude = kwargs.get('creative_latitude', 0.7) - url = kwargs.get('url',None) - tokens = kwargs.get('tokens', 500) - image = kwargs.get('image', None) - prompt = kwargs.get('prompt', None) - instruction = kwargs.get('instruction', "") - example_list = kwargs.get('example_list', []) - add_params = kwargs.get('add_params', None) - - - - request_type = self.cFig.lm_request_mode - - response = None - CGPT_response = "" - - self.cFig.lm_url = url - if not self.cFig.is_lm_server_up: - self.j_mngr.log_events("Local or remote server is not responding, may be unable to send data.", - TroubleSgltn.Severity.WARNING, - True) - - #if there's an image here - if image and request_type == self.mode.OSSIMPLE: - self.j_mngr.log_events("The AI Service using 'Simplfied Data' can't process an image. The image will be disregarded in generated output.", - TroubleSgltn.Severity.INFO, - True) - image = None - - if image: - #The user is on their own to make - #the right model selection for handling an image - - if isinstance(image, torch.Tensor): #just to be sure - image = self.dalle.tensor_to_base64(image) - - if not isinstance(image,str): - image = None - self.j_mngr.log_events("Image file is invalid. Image will be disregarded in the generated output.", - TroubleSgltn.Severity.WARNING, - True) - - key = "" - if request_type == self.mode.OPENAI: - key = self.cFig.key - elif request_type == self.mode.OPENSOURCE or request_type == self.mode.LMSTUDIO: - key = self.cFig.lm_key - elif request_type == self.mode.GROQ: - key = self.cFig.groq_key - else: - self.j_mngr.log_events("No LLM key value found", - TroubleSgltn.Severity.WARNING, - True) - - headers = self.utils.build_web_header(key) - - if request_type == self.mode.OSSIMPLE or not image: - messages = self.utils.build_data_basic(prompt, example_list, instruction) #Some apps can't handle an embedded list of role:user dicts - self.j_mngr.log_events("Using Basic data structure", - TroubleSgltn.Severity.INFO, - True) - else: - messages = self.utils.build_data_multi(prompt,instruction,example_list, image) - self.j_mngr.log_events("Using Complex data structure", - TroubleSgltn.Severity.INFO, - True) - - params = { - "model": GPTmodel, - "messages": messages, - "temperature": creative_latitude, - "max_tokens": tokens - } - - if add_params: - add_keys =['param','value'] - self.j_mngr.append_params(params, add_params, add_keys) - - - post_success = False - response_json = "" - #payload = {**params} - try: - response = requests.post(url, headers=headers, json=params, timeout=(12,120)) - - if response.status_code in range(200, 300): - response_json = response.json() - if response_json and not 'error' in response_json: - CGPT_response = self.utils.clean_response_text(response_json['choices'][0]['message']['content'] ) - post_success = True - else: - error_message = response_json.get('error', 'Unknown error') - self.j_mngr.log_events(f"Server was unable to process the response. Error: {error_message}", - TroubleSgltn.Severity.ERROR, - True) - else: - CGPT_response = 'Server was unable to process this request' - self.j_mngr.log_events(f"Server was unable to process the request. Status: {response.status_code}: {response.text}", - TroubleSgltn.Severity.ERROR, - True) - - except Exception as e: - self.j_mngr.log_events(f"Unable to send data to server. Error: {e}", - TroubleSgltn.Severity.ERROR, - True) - if post_success: - try: - rpt_model = response_json['model'] - rpt_usage = response_json['usage'] - if rpt_model: - self.j_mngr.log_events(f"Using LLM: {rpt_model}", - is_trouble=True) - if rpt_usage: - self.j_mngr.log_events(f"Tokens Used: {rpt_usage}", - is_trouble=True) - - except Exception as e: - self.j_mngr.log_events(f"Unable to report some completion information: model, usage. Error: {e}", - TroubleSgltn.Severity.INFO, - True) - - return CGPT_response - - -class ooba_web_request(Request): - - - def request_completion(self, **kwargs): - - """ - Accesses an OpenAI API client and uses the incoming arguments to construct a JSON that contains the request for an LLM response. - Sends the request via the client. Handles the OpenAI return object and extacts the model and the response from it. - - Args: - GPTmodel (str): The ChatGPT model to use in processing the request. Alternately this serves as a flag that the function will processing open source LLM data (GPTmodel = "LLM") - creative_latitude (float): A number setting the 'temperature' of the LLM - tokens (int): A number indicating the max number of tokens used to process the request and response - url (str): The url for the server the information is being sent to - request_:type (Enum): Specifies whether the function will be using a ChatGPT configured api object or an third party/url configured api object. - prompt (str): The users' request to action by the LLM - instruction (str): Text describing the conditions and specific requirements of the return value - image (b64 JSON/str): An image to be evaluated by the LLM in the context of the instruction - - Return: - A string consisting of the LLM's response to the instruction and prompt in the context of any image and/or file - """ - GPTmodel = kwargs.get('model', "") - creative_latitude = kwargs.get('creative_latitude', 0.7) - url = kwargs.get('url',None) - tokens = kwargs.get('tokens', 500) - image = kwargs.get('image', None) - prompt = kwargs.get('prompt', None) - instruction = kwargs.get('instruction', "") - example_list = kwargs.get('example_list', []) - request_type = self.cFig.lm_request_mode - add_params = kwargs.get('add_params', None) - - - response = None - CGPT_response = "" - - url = self.utils.validate_and_correct_url(url) #validate v1/chat/completions path - - self.cFig.lm_url = url - if not self.cFig.is_lm_server_up: - self.j_mngr.log_events("Local server is not responding, may be unable to send data.", - TroubleSgltn.Severity.WARNING, - True) - - #image code is here, but right now none of the tested LLM front ends can handle them - #when using an http POST - if image: - image = None - self.j_mngr.log_events('Images not supported in this mode at this time. Image not transmitted', - TroubleSgltn.Severity.WARNING, - True) - - key = "" - if request_type == self.mode.OPENAI: - key = self.cFig.key - else: - key = self.cFig.lm_key - - headers = self.utils.build_web_header(key) - - #messages = self.utils.build_data_basic(prompt, example_list, instruction) - - messages = self.utils.build_data_ooba(prompt, example_list, instruction) - - if request_type == self.mode.OOBABOOGA: - self.j_mngr.log_events(f"Processing Oobabooga http: POST request with url: {url}", - is_trouble=True) - params = { - "model": GPTmodel, - "messages": messages, - "temperature": creative_latitude, - "max_tokens": tokens, - "user_bio": "", - "user_name": "" - } - else: - params = { "model": GPTmodel, "messages": messages, "temperature": creative_latitude, "max_tokens": tokens - } + } - # Add the parameter if it exists if add_params: - add_keys =['param','value'] - self.j_mngr.append_params(params, add_params, add_keys) + self.j_mngr.append_params(params, add_params, ['param', 'value']) - post_success = False - response_json = "" - #payload = {**params} try: - response = requests.post(url, headers=headers, json=params, timeout=(12,120)) - - if response.status_code in range(200, 300): - response_json = response.json() - if response_json and not 'error' in response_json: - CGPT_response = self.utils.clean_response_text(response_json['choices'][0]['message']['content'] ) - post_success = True - else: - error_message = response_json.get('error', 'Unknown error') - self.j_mngr.log_events(f"Server was unable to process the response. Error: {error_message}", - TroubleSgltn.Severity.ERROR, - True) - else: - CGPT_response = 'Server was unable to process this request' - self.j_mngr.log_events(f"Server was unable to process the request. Status: {response.status_code}: {response.text}", - TroubleSgltn.Severity.ERROR, - True) - - except Exception as e: - self.j_mngr.log_events(f"Unable to send data to server. Error: {e}", - TroubleSgltn.Severity.ERROR, - True) - if post_success: - try: - rpt_model = response_json['model'] - rpt_usage = response_json['usage'] - if rpt_model: - self.j_mngr.log_events(f"Using LLM: {rpt_model}", - is_trouble=True) - if rpt_usage: - self.j_mngr.log_events(f"Tokens Used: {rpt_usage}", - is_trouble=True) + response = self.retry_handler.execute_with_retry( + self._make_request, + self.RequestType.COMPLETION, + client, + params + ) #_make_request is passed as a wrapped function, the arguments that follow are passed into + #args which is unpacked as a tuple in _make_request() - except Exception as e: - self.j_mngr.log_events(f"Unable to report some completion information: model, usage. Error: {e}", - TroubleSgltn.Severity.INFO, - True) + if response and response.choices and 'error' not in response: + self._log_completion_metrics(response) + CGPT_response = self.utils.clean_response_text( + response.choices[0].message.content + ) + else: + err_mess = getattr(response, 'error', "Error message missing") + self.j_mngr.log_events( + f"Server was unable to process this request. Error: {err_mess}", + TroubleSgltn.Severity.ERROR, + True + ) + CGPT_response = "Server was unable to process the request" + + except Exception as e: + self.j_mngr.log_events( + f"Request failed: {str(e)}", + TroubleSgltn.Severity.ERROR, + True + ) + CGPT_response = "Server was unable to process the request" return CGPT_response - class claude_request(Request): - - def request_completion(self, **kwargs): - + """Concrete class for Claude/Anthropic API requests""" + + def request_completion(self, **kwargs) -> str: claude_model = kwargs.get('model') creative_latitude = kwargs.get('creative_latitude', 0.7) - tokens = kwargs.get('tokens',500) + tokens = kwargs.get('tokens', 500) prompt = kwargs.get('prompt', "") instruction = kwargs.get('instruction', "") - file = kwargs.get('file',"") image = kwargs.get('image', None) example_list = kwargs.get('example_list', []) add_params = kwargs.get('add_params', None) - - request_type = self.cFig.lm_request_mode - - response = None claude_response = "" - file += file.strip() - client = None - - if request_type == self.mode.CLAUDE: - client = self.cFig.anthropic_client - + client = self.cFig.anthropic_client + self._initialize_retry_handler(**kwargs) if not client: - if request_type == self.mode.CLAUDE: - self.j_mngr.log_events("Invalid or missing anthropic API key (Claude). Keys must be stored in an environment variable (see: ReadMe). Claude request aborted", - TroubleSgltn.Severity.ERROR, - True) - claude_response = "Invalid or missing anthropic API key. Keys must be stored in an environment variable (see: ReadMe). Claude request aborted" - return claude_response + self.j_mngr.log_events( + "Invalid or missing Anthropic API key. Keys must be stored in an environment variable.", + TroubleSgltn.Severity.ERROR, + True + ) + return "Invalid or missing Anthropic API key" - #there's an image - if image: - # Use the user's selected vision model if it's what was chosen, - #otherwise use the last vision model in the list - #If the user is using a local LLM they're on their own to make - #the right model selection for handling an image - - if isinstance(image, torch.Tensor): #just to be sure - image = self.dalle.tensor_to_base64(image) - - if not isinstance(image,str): - image = None - self.j_mngr.log_events("Image file is invalid. Image will be disregarded in the generated output.", - TroubleSgltn.Severity.WARNING, - True) - - messages = [] + # Process image if present + image = self._process_image(image) + # Build messages messages = self.utils.build_data_claude(prompt, example_list, image) - - if not prompt and not image and not instruction and not example_list: - # User has provided no prompt, file or image - claude_response = "Photograph of an stained empty box with 'NOTHING' printed on its side in bold letters, small flying moths, dingy, gloomy, dim light rundown warehouse" - self.j_mngr.log_events("No instruction and no prompt were provided, the node was only able to provide a 'Box of Nothing'", - TroubleSgltn.Severity.WARNING, - True) - return claude_response + # Handle empty input case + if not any([prompt, image, instruction, example_list]): + return "Empty request, no input provided" + + # Prepare request parameters params = { - "model": claude_model, - "messages": messages, - "temperature": creative_latitude, - "system": instruction, - "max_tokens": tokens + "model": claude_model, + "messages": messages, + "temperature": creative_latitude, + "system": instruction, + "max_tokens": tokens } - # Add the parameter if it exists if add_params: - add_keys =['param','value'] - self.j_mngr.append_params(params, add_params, add_keys) + self.j_mngr.append_params(params, add_params, ['param', 'value']) try: - response = client.messages.create(**params) + response = self.retry_handler.execute_with_retry( + self._make_request, + self.RequestType.ANTHROPIC, + client, + params + ) + + if response and 'error' not in response: + self._log_completion_metrics(response) + try: + claude_response = response.content[0].text + claude_response = self.utils.clean_response_text(claude_response) + except (IndexError, AttributeError): + claude_response = "No valid data was returned" + self.j_mngr.log_events( + "Claude response was not valid data", + TroubleSgltn.Severity.WARNING, + True + ) + else: + claude_response = "Server was unable to process the request" + self.j_mngr.log_events( + 'Server was unable to process this request.', + TroubleSgltn.Severity.ERROR, + True + ) - except anthropic.AuthenticationError as e: - self.j_mngr.log_events(f"Authentication error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.ERROR, - True) - except anthropic.PermissionDeniedError as e: - self.j_mngr.log_events(f"Permission denied error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.ERROR, - True) - except anthropic.NotFoundError as e: - self.j_mngr.log_events(f"Not found error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.ERROR, - True) - except anthropic.RateLimitError as e: - self.j_mngr.log_events(f"Rate limit exceeded error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.WARNING, - True) - except anthropic.BadRequestError as e: - self.j_mngr.log_events(f"Bad request error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.ERROR, - True) - except anthropic.InternalServerError as e: - self.j_mngr.log_events(f"Internal server error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.ERROR, - True) except Exception as e: - self.j_mngr.log_events(f"Unexpected error: {request_utils.parse_anthropic_error(e)}", - TroubleSgltn.Severity.ERROR, - True) - - - if response and 'error' not in response: - rpt_model = "" - try: - rpt_model = response.model - rpt_usage = response.usage - - if rpt_model: - self.j_mngr.log_events(f"Using LLM: {rpt_model}", - is_trouble=True) - if rpt_usage: - self.j_mngr.log_events(f"Tokens Used: {rpt_usage}", - TroubleSgltn.Severity.INFO, - True) - except Exception as e: - self.j_mngr.log_events(f"Unable to report some completion information, error: {e}", - TroubleSgltn.Severity.INFO, - True) - try: - claude_response = response.content[0].text - except (IndexError, AttributeError): - claude_response = "No data was returned" - self.j_mngr.log_events("Claude response was not valid data", - TroubleSgltn.Severity.WARNING, - True) - claude_response = self.utils.clean_response_text(claude_response) - else: + error_msg = self.utils.parse_anthropic_error(e) + self.j_mngr.log_events( + f"Request failed: {error_msg}", + TroubleSgltn.Severity.ERROR, + True + ) claude_response = "Server was unable to process the request" - self.j_mngr.log_events('Server was unable to process this request.', - TroubleSgltn.Severity.ERROR, - True) + return claude_response + + +class oai_web_request(Request): + """Concrete class for OpenAI-compatible web requests""" + + def request_completion(self, **kwargs) -> str: + GPTmodel = kwargs.get('model', "") + creative_latitude = kwargs.get('creative_latitude', 0.7) + url = kwargs.get('url', None) + tokens = kwargs.get('tokens', 500) + image = kwargs.get('image', None) + prompt = kwargs.get('prompt', None) + instruction = kwargs.get('instruction', "") + example_list = kwargs.get('example_list', []) + add_params = kwargs.get('add_params', None) + + CGPT_response = "" + request_type = self.cFig.lm_request_mode + self._initialize_retry_handler(**kwargs) + + # URL setup and validation + self.cFig.lm_url = url + if not self.cFig.is_lm_server_up: + self.j_mngr.log_events( + "Local or remote server is not responding, may be unable to send data.", + TroubleSgltn.Severity.WARNING, + True + ) + + # Process image if present + if image and request_type == self.mode.OSSIMPLE: + self.j_mngr.log_events( + "The AI Service using 'Simplified Data' can't process an image. The image will be disregarded in generated output.", + TroubleSgltn.Severity.INFO, + True + ) + image = None + else: + image = self._process_image(image) + + # Get appropriate key for request type + key = self._get_key_for_request_type(request_type) + headers = self.utils.build_web_header(key) + + # Build message structure + if request_type == self.mode.OSSIMPLE or not image: + messages = self.utils.build_data_basic(prompt, example_list, instruction) + self.j_mngr.log_events( + "Using Basic data structure", + TroubleSgltn.Severity.INFO, + True + ) + else: + messages = self.utils.build_data_multi(prompt, instruction, example_list, image) + self.j_mngr.log_events( + "Using Complex data structure", + TroubleSgltn.Severity.INFO, + True + ) + + # Prepare request parameters + params = { + "model": GPTmodel, + "messages": messages, + "temperature": creative_latitude, + "max_tokens": tokens + } + + if add_params: + self.j_mngr.append_params(params, add_params, ['param', 'value']) + + try: + response = self.retry_handler.execute_with_retry( + self._make_request, + self.RequestType.POST, + url, + headers, + params + ) + + if response.status_code in range(200, 300): + response_json = response.json() + if response_json and 'error' not in response_json: + CGPT_response = self.utils.clean_response_text( + response_json['choices'][0]['message']['content'] + ) + self._log_completion_metrics(response_json, "json") + else: + error_message = response_json.get('error', 'Unknown error') + self.j_mngr.log_events( + f"Server error in response: {error_message}", + TroubleSgltn.Severity.ERROR, + True + ) + CGPT_response = "Server was unable to process the request" + else: + self.j_mngr.log_events( + f"Server error status: {response.status_code}: {response.text}", + TroubleSgltn.Severity.ERROR, + True + ) + CGPT_response = "Server was unable to process the request" + + except Exception as e: + self.j_mngr.log_events( + f"Request failed: {str(e)}", + TroubleSgltn.Severity.ERROR, + True + ) + CGPT_response = "Server was unable to process the request" + + return CGPT_response + + def _get_key_for_request_type(self, request_type: RequestMode) -> str: + """Get appropriate key based on request type""" + if request_type == self.mode.OPENAI: + return self.cFig.key + elif request_type in [self.mode.OPENSOURCE, self.mode.LMSTUDIO]: + return self.cFig.lm_key + elif request_type == self.mode.GROQ: + return self.cFig.groq_key + return "" + +class ooba_web_request(Request): + """Concrete class for Oobabooga web requests""" + + def request_completion(self, **kwargs) -> str: + GPTmodel = kwargs.get('model', "") + creative_latitude = kwargs.get('creative_latitude', 0.7) + url = kwargs.get('url', None) + tokens = kwargs.get('tokens', 500) + prompt = kwargs.get('prompt', None) + instruction = kwargs.get('instruction', "") + example_list = kwargs.get('example_list', []) + add_params = kwargs.get('add_params', None) + + CGPT_response = "" + request_type = self.cFig.lm_request_mode + self._initialize_retry_handler(**kwargs) + + # URL setup and validation + url = self.utils.validate_and_correct_url(url) + self.cFig.lm_url = url + + if not self.cFig.is_lm_server_up: + self.j_mngr.log_events( + "Local server is not responding, may be unable to send data.", + TroubleSgltn.Severity.WARNING, + True + ) + + # Get appropriate key + key = self.cFig.key if request_type == self.mode.OPENAI else self.cFig.lm_key + headers = self.utils.build_web_header(key) + + # Build messages with Oobabooga-specific format + messages = self.utils.build_data_ooba(prompt, example_list, instruction) + + # Prepare request parameters + params = { + "model": GPTmodel, + "messages": messages, + "temperature": creative_latitude, + "max_tokens": tokens, + } + + # Add Oobabooga-specific parameters + if request_type == self.mode.OOBABOOGA: + self.j_mngr.log_events( + f"Processing Oobabooga http: POST request with url: {url}", + is_trouble=True + ) + params.update({ + "user_bio": "", + "user_name": "" + }) + + if add_params: + self.j_mngr.append_params(params, add_params, ['param', 'value']) + + try: + response = self.retry_handler.execute_with_retry( + self._make_request, + self.RequestType.POST, + url, + headers, + params + ) + + if response.status_code in range(200, 300): + response_json = response.json() + if response_json and 'error' not in response_json: + CGPT_response = self.utils.clean_response_text( + response_json['choices'][0]['message']['content'] + ) + self._log_completion_metrics(response_json, "json") + else: + error_message = response_json.get('error', 'Unknown error') + self.j_mngr.log_events( + f"Server error in response: {error_message}", + TroubleSgltn.Severity.ERROR, + True + ) + else: + CGPT_response = "Server was unable to process the request" + self.j_mngr.log_events( + f"Server error status: {response.status_code}: {response.text}", + TroubleSgltn.Severity.ERROR, + True + ) + + except Exception as e: + self.j_mngr.log_events( + f"Request failed: {str(e)}", + TroubleSgltn.Severity.ERROR, + True + ) + CGPT_response = "Server was unable to process the request" + + return CGPT_response class dall_e_request(Request): + """Concrete class for DALL-E image generation requests""" def __init__(self): - super().__init__() # Ensures common setup from Request - self.trbl = TroubleSgltn() + super().__init__() + self.trbl = TroubleSgltn() self.iu = ImageUtils() + # Override with DALL-E specific retry config + retry_config = RetryConfigFactory.create_config(self.cFig.lm_request_mode) + self.retry_handler = RetryHandler(retry_config, self.j_mngr) - def request_completion(self, **kwargs)->tuple[torch.Tensor, str]: + def request_completion(self, **kwargs) -> Tuple[torch.Tensor, str]: GPTmodel = kwargs.get('model') prompt = kwargs.get('prompt') - image_size = kwargs.get('image_size') + image_size = kwargs.get('image_size') image_quality = kwargs.get('image_quality') style = kwargs.get('style') batch_size = kwargs.get('batch_size', 1) self.trbl.set_process_header('Dall-e Request') - batched_images = torch.zeros(1, 1024, 1024, 3, dtype=torch.float32) - revised_prompt = "Image and mask could not be created" # Default prompt message - - if not self.cFig.openaiClient: - self.j_mngr.log_events("OpenAI API key is missing or invalid. Key must be stored in an enviroment variable (see ReadMe). This node is not functional.", - TroubleSgltn.Severity.WARNING, - True) - return(batched_images, revised_prompt) - - client = self.cFig.openaiClient - - - self.j_mngr.log_events(f"Talking to Dalle model: {GPTmodel}", - is_trouble=True) + revised_prompt = "Image and mask could not be created" + + client = self.cFig.openaiClient + self._initialize_retry_handler(**kwargs) + + if not client: + self.j_mngr.log_events( + "OpenAI API key is missing or invalid. Key must be stored in an environment variable.", + TroubleSgltn.Severity.WARNING, + True + ) + return batched_images, revised_prompt + + self.j_mngr.log_events( + f"Talking to Dalle model: {GPTmodel}", + is_trouble=True + ) - have_rev_prompt = False images_list = [] + have_rev_prompt = False for _ in range(batch_size): - try: + params = { + "model": GPTmodel, + "prompt": prompt, + "size": image_size, + "quality": image_quality, + "style": style, + "n": 1, + "response_format": "b64_json" + } - response = client.images.generate( - model = GPTmodel, - prompt = prompt, - size = image_size, - quality = image_quality, - style = style, - n=1, - response_format = "b64_json", - ) - - # Get the revised_prompt - if response and not 'error' in response: + try: + response = self.retry_handler.execute_with_retry( + self._make_request, + self.RequestType.IMAGE, + client, + params + ) + + if response and 'error' not in response: if not have_rev_prompt: revised_prompt = response.data[0].revised_prompt have_rev_prompt = True - #Convert the b64 json to a pytorch tensor + b64Json = response.data[0].b64_json if b64Json: png_image, _ = self.dalle.b64_to_tensor(b64Json) images_list.append(png_image) else: - self.j_mngr.log_events(f"Dalle-e could not process an image in your batch of: {batch_size} ", - TroubleSgltn.Severity.WARNING, - True) - - else: - self.j_mngr.log_events(f"Dalle-e could not process an image in your batch of: {batch_size} ", - TroubleSgltn.Severity.WARNING, - True) - except openai.APIConnectionError as e: - self.j_mngr.log_events(f"ChatGPT server connection error in an image in your batch of {batch_size} Error: {e.__cause__}", - TroubleSgltn.Severity.ERROR, - True) - except openai.RateLimitError as e: - self.j_mngr.log_events(f"ChatGPT RATE LIMIT error in an image in your batch of {batch_size} Error: {e}: {e.response}", - TroubleSgltn.Severity.ERROR, - True) - time.sleep(0.5) - except openai.APIStatusError as e: - self.j_mngr.log_events(f"ChatGPT STATUS error in an image in your batch of {batch_size}; Error: {e.status_code}:{e.response}", - TroubleSgltn.Severity.ERROR, - True) + self.j_mngr.log_events( + f"Dalle-e could not process an image in your batch of: {batch_size}", + TroubleSgltn.Severity.WARNING, + True + ) + except Exception as e: - self.j_mngr.log_events(f"An unexpected error in an image in your batch of {batch_size}; Error:{e}", - TroubleSgltn.Severity.ERROR, - True) - - + self.j_mngr.log_events( + f"Failed to generate image {_ + 1}/{batch_size}: {str(e)}", + TroubleSgltn.Severity.ERROR, + True + ) + if images_list: count = len(images_list) - self.j_mngr.log_events(f'{count} images were processed successfully in your batch of: {batch_size}', - is_trouble=True) - + self.j_mngr.log_events( + f'{count} images were processed successfully in your batch of: {batch_size}', + is_trouble=True + ) batched_images = torch.cat(images_list, dim=0) else: - self.j_mngr.log_events(f'No images were processed in your batch of: {batch_size}', - TroubleSgltn.Severity.WARNING, - is_trouble=True) + self.j_mngr.log_events( + f'No images were processed in your batch of: {batch_size}', + TroubleSgltn.Severity.WARNING, + is_trouble=True + ) + self.trbl.pop_header() - return(batched_images, revised_prompt) + return batched_images, revised_prompt - def modify_image(self, client, model, image_bytes, prompt, image_size): - """This is an unused stub to be used if Dall-e-3 ever implements image to image edits""" - image_bytes.seek(0) # Ensure the buffer is at the beginning - - response = client.images.edit( - model=model, - image=image_bytes, - prompt=prompt, - n=1, - size=image_size, - response_format = "b64_json" - ) - - return response - class request_context: def __init__(self)-> None: self._request = None @@ -1020,7 +1255,4 @@ class request_utils: elif hasattr(e, 'message'): return e.message else: - return str(e) - - - \ No newline at end of file + return str(e) diff --git a/fetch_models.py b/fetch_models.py index df7412b..eb3cbb7 100644 --- a/fetch_models.py +++ b/fetch_models.py @@ -19,6 +19,7 @@ class RequestMode(Enum): OSSIMPLE = 7 LMSTUDIO = 8 OLLAMA = 9 + DALLE = 10 class ModelFetchStrategy(ABC): @@ -183,7 +184,7 @@ class FetchModels: self.strategy = FetchByMethod() elif request_type == RequestMode.CLAUDE: - model_names = ['claude-3-haiku-20240307', 'claude-3-5-haiku-20241022', 'claude-3-sonnet-20240229', 'claude-3-5-sonnet-20240620', 'claude-3-5-sonnet-latest', 'claude-3-opus-20240229'] + model_names = ['claude-3-haiku-20240307', 'claude-3-sonnet-20240229', 'claude-3-5-sonnet-20240620', 'claude-3-5-sonnet-latest', 'claude-3-opus-20240229'] return ModelsContainer(model_names) elif request_type == RequestMode.GEMINI: diff --git a/help.json b/help.json index 9d0c43e..3a5c7e2 100644 --- a/help.json +++ b/help.json @@ -1,10 +1,9 @@ { "sp_help": "• Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n\n****************\n\n\n✦ AI_Selection [input connection]: Attach the Plush 'AI_Chooser' Node to this input so you can select the AI_Service and model you want to use. As of v1.21.11 ChatGPT, Anthropic & Groq services and models are available. \n\n✦ creative_latitude: Higher numbers give the model more freedom to interpret your prompt or image. Lower numbers constrain the model to stick closely to your input.\n\n✦ tokens: A limit on how many tokens are made available for ChatGPT to use, it doesn't have to use them all.\n\n✦ style: Choose the art style you want to base your prompt on. If this list is too long, type a few characters of the style you're looking for and the list will dynamically filter.\n\n✦ artist: Will produce a 'style of' phrase listing the number of artists you indicate. They will be artists that work in the chosen style. Choose 0 if you don't want this.\n\n✦ prompt_style: 'Narrative' is long form grammatically correct creative writing, This is the preferred form for Dall-e. 'Tags' is a terse, stripped down list of visual attributes without grammatical phrasing, This is the preferred form for SD and Midjourney.\n\n✦ max_elements: A limit on the number of distinct descriptions of visual elements in the prompt. Smaller numbers makes a shorter prompt.\n\n✦ style_info: Set to True if you want background information about the art style you chose.", "wrangler_help": "• Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n• Exif Wrangler will extract Exif and/or AI generation workflow metadata from .jpg (.jpeg) and .png images. .jpg photographs can be queried for their camera settings. ComfyUI's .png files will yield certain values from their workflow including the prompt, seed etc. Images from other AI generators may or may not yield data depending on where they store their metadata. For instance Auto 1111 .jpg's will yield their workflow information that's stored in their Exif comment.\n\n**************\n \n✦ write_to_file: Whether or not to save the meta data file you see in the output to a .txt file in the: '.../ComfyUI/output/PlushFiles' directory.\n\n✦ file_prefix: The prefix for the file name of the saved file, this will be appended to a date/time value to make the file unique. The file will have a .txt extension: e.g., 'MyFileName_ew_20240204_193224.txt'\n\n✦ Min_Prompt_len: A filter value for prompts: Exif Wrangler has to distinguish between actual prompts and other long strings in the ComfyUI embeded meta data. Every Note, every text display box, and even some text that's hidden in nodes is included in the JSON that holds this information. This field allows you to set a minimum length for strings to be displayed to help filter out shorter unwanted text strings.\n\n✦ Alpha_Char_Pct: Another prompt filter that works by only allowing text strings that have a percentage of alpha ASCII characters (Aa - Zz plus comma) equal to or higher than this setting. Increasing the percentage screens out strings that have lots of bytes, symbols and numbers. If you use a lot of weightings or Lora values in your prompts that introduce angle brackets, parentheses, brackets and colons, you may have to lower this percentage to see your prompt. \n\n✦ Prompt_Filter_Term: Enter a single term or short phrase here. A particular prompt string will only be included in Possible Prompts if it contains an exact match for this term. This can be used in a couple of ways: \n 1) If you know there's a term you always or frequently use in the prompts, or if you remember part of a particular image prompt's wording, you can add it here before you click the Queue button. \n 2) If, after clicking Queue, a lot of Possible Prompt candidates clutter your output. Find the one you know is the actual prompt, find a unique word or phrase in it e.g.: 'regal'. Enter that word or phrase as a filter term and run Wrangler again. You'll get back an uncluttered response to save as a file.\n\n***************\n\n✦ troubleshooting output: Hook this output up to a text display node to see any INFO/WARNING/ERROR data that's generated during this node's run. ", - "dalle_help": "• Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n• Dall-e Image will produce an image .PNG from a text prompt using the Dall-e 3 model from OpenAI. It requires a OpenAI API key.\n\n**************\n\n✦ GPTmodel: The Dall-e model that will generate the image file. Currently this is limited to Dall-e 3.\n\n✦ prompt: The text prompt for the image you want to produce. Be aware that OpenAI will generate their own prompt from your prompt and pass that to the image model.\n\n✦ image_size: Choose a square, portrait or landscape image. The image size format is: Width, Height. The 1792 image sizes cost slightly more tokens.\n\n✦ image_quality: Self explanatory, you can experiment to see if you think there's a noticable difference. The standard quality image costs a few less tokens than hd.\n\n✦ style: Vivid produces a little more contrast and more saturated colors. The choice depends on what type of image you're trying to produce.\n\n✦ batch_size: The number of images you want to produce in one run. The vast majority of the times batches run without incident, but you should be aware that sending image requests to the Dall-e server is not as reliable as running images locally in SD. If the server gets overtaxed, or hiccups you may not get back all the images you requested. This Dall-e node will handle OpenAI server errors gracefully and allow your batch to continue to completion, but sometimes you may get back fewer images than you requested. If you keep the 'troubleshooting' output connected it will report any errors and let you know how many images were processed vs how many you requested.\n\n✦ seed: This works just like a seed in a KSampler except that it doesn't affect a latent or the image. It's simply there for you to set to: 'randomize' or 'increment' if you want Dall-e to run with every Queue, or to 'fixed' if you only want Dall-e to run once per prompt or setting. The Dall_e API doesn't actually pass seed values. This can also be controlled by the 'Global Seed' from the Inspire Pack. \n\n***************\n\n✦ troubleshooting output: Hook this output up to a text display node to see any INFO/WARNING/ERROR data that's generated during this node's run.\n\n✦ Dalle_e_prompt: The prompt that Dall-e 3 generates from your prompt. This is the prompt that actually gets passed to the image model. Hook up a text display node to see it.", - "adv_prompt_help": "• Advanced Prompt Enhancer (APE) uses AI Models to generate text output from any combination of: Instruction, Example_or_Context, Image and Prompt you provide. No API key is needed for Open source Models. This node can use various remote models, ChatGPT, Groq and Anthropic Claude if you have an API key and have stored it in an environment variable (see ReadMe file). With or without a key it can also connect to various local apps and models e.g.: LM Studio, Oobabooga, Koboldcpp, etc.\n\n• image input: Advanced Prompt Enhancer can send image data (in the form of a b64 image file) to AI vision capable models. If you're sending an image to an AI model be sure both the model and the app or remote service have vision capabilities and can handle image files.\n\n• Examples_or_Context: APE can send example(s) and/or context along with your instructions to the LLM. Examples and Context *always* need to be in the form of: User input, then the delimiter, followed by the model's response. Delimited text entered in this field will automatically create alternatating input to the model for each delimited segment using this pattern. If you want to explicitly tag your text as being user or model input you can preface each delimited segment with <> or <>}. (There's an workflow file: 'How_To_Use_Examples.png' in the 'Example_Worflows' folder with details about using the Examples_or_Context input.)\n\n•Context (output): The 'Context' output is an accumulation of the 'Examples_or_Context' input plus the current 'Prompt' and 'LLM_response'. It can be fed directly into the 'Examples_or_Context' input of a second APE node. Before passing this information between nodes, make sure all the Context linked nodes have the same 'example_delimiter' setting. Each node linked in this way will accumulate all of the conversations of the nodes before it.\n\n• API Keys: API keys need to be kept in environment variables. The Environment Variable names that Advanced Prompt Enhancer looks for are: ✦ChatGPT: OPENAI_API_KEY or OAI_KEY; ✦Groq: GROQ_API_KEY; ✦Anthropic: ANTHROPIC_API_KEY. Find instructions on how to create the Enviroment Variable here: https://github.com/glibsonoran/Plush-for-ComfyUI?tab=readme-ov-file#requirements . \n\n**************\n\n• AI_service: This indicates the type of AI service and connection you're going to send your data to. If you're using a local AI app you'll need to provide a valid URL in the LLM_URL field near the bottom of the node. If you're using 'Oobabooga API' make sure you read the LLM_URL help below. 'OpenAI compatible http POST' uses a web POST action rather than the OpenAI API Object to communicate with the local or remote AI server, typically this requires a 'v1/chat/completions' path in the URL. 'http POST Simplified Data' also uses a web POST action and presents a simplified data structure. Try this if the other AI service methods don't work, it will also require a: 'v1/chat/completions' path. \n\n• GPTmodel: This field only applies when the LLM field is set to 'ChatGPT'. Select the specific OpenAI ChatGPT model you want to use. If you're inputting an image, make sure the model you choose is vision capable.\n\n• Groq_model: This only applies when you select 'Groq' in the AI_service field. Choose the Groq model you want to use. \n\n• Anthropic_model: This only applies when you select 'Anthropic' from the AI_service field. Choose the Anthopic model you want to use. \n\n• Ollama_model: This will display the model(s) currently loaded in the Ollama front end. In order for models to show up in the drop down Ollama will have to be running with the models you intend to use loaded *before* starting ComfyUI. Note that APE looks for the standard url: http://localhost:11434/api/tags when retrieving the model names. If you've setup Ollama with another url (e.g. different port), you'll need to modify the 'urls.json' file. \n\n• Optional_model: This is a list of models extracted from the text file: '/custom_nodes/Plush-for-ComfyUI/Opt_models.txt'. This is a user configurable file that's initially empty. It's meant to hold model names for unique remote or local AI services that require a model name to be included with the inference request. These model names only apply to AI_Services that end in '(URL)'. If you enter or remove model names from this file, the changes will only show up after you reboot ComfyUI. Instructions on how to enter these model names is in the comments header of the 'Opt_models.txt' text file. \n\n• creative_latitude: (Temperature) This will set how strictly the LLM adheres to common word relationships and how closely it will follow your instruction and prompt. Setting this value higher allows more creative freedom in interpreting your input and generating its ouptput.\n\n• tokens: The maximum number of tokens that the LLM can use in processing your prompt and return text. This is not the number of tokens it 'will' use, it's the number available that it 'can' use.\n\n• seed: This is a pseudo or mock seed, it has no effect on the text generated, and it's not passed to the LLM. It's used here solely to control when the node will run. It works the same as a KSampler, set it to 'fixed' if you want the node to run only once each time you change your inputs, set it to random or increment/decrement if you want it run with each Queue.\n\n• example_delimiter: You can provide multiple examples or context to the LLM. Providing multiple examples for a given instruction is a type of 'Few Shot Prompting', which can be effective with some LLM's. This field indicates how the node will distinguish each separate example, each separate example or context item will be presented as originating from the User then the Model alternating in that order for as many as you enter. You can choose to separate your examples with a pipe '|' character, two newlines (i.e.: carriage returns) or two colons '::', these are called delimiters and they denote where these separations will occur.\n\n• LLM_URL: When using an LLM other than ChatGPT, Anthropic or Groq you'll need to provide a URL in this field. Typically the AI application you're using (e.g. LM Studio, Oobabooga), will indicate the URL to use after you startup its server. It may be in the terminal output or in the UI. Some local AI apps will specify that a particular URL is OpenAI compatible, if so this is the one you want to use. Typically the URLs for local apps have this general format: http://localhost:5001/v1 where '5001' is the port and 'localhost' is interchangable with '127.0.0.1'. If you're using the Oobabooga API or 'OpenAI compatible http POST' selection your url will need to have /chat/completions appended as part of the url: http://127.0.0.1:5000/v1/chat/completions\n\n**************\n\n• Use the troubleshooting output if you have issues with model connections, or if you want to see exactly which model was used to produce your output (some ChatGPT model names are actually only pointers to the latest specific model in that category) and how many tokens were used.", + "dalle_help": "• Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n• Dall-e Image will produce an image .PNG from a text prompt using the Dall-e 3 model from OpenAI. It requires a OpenAI API key.\n\n**************\n\n✦ GPTmodel: The Dall-e model that will generate the image file. Currently this is limited to Dall-e 3.\n\n✦ prompt: The text prompt for the image you want to produce. Be aware that OpenAI will generate their own prompt from your prompt and pass that to the image model.\n\n✦ image_size: Choose a square, portrait or landscape image. The image size format is: Width, Height. The 1792 image sizes cost slightly more tokens.\n\n✦ image_quality: Self explanatory, you can experiment to see if you think there's a noticable difference. The standard quality image costs a few less tokens than hd.\n\n✦ style: Vivid produces a little more contrast and more saturated colors. The choice depends on what type of image you're trying to produce.\n\n✦ batch_size: The number of images you want to produce in one run. The vast majority of the times batches run without incident, but you should be aware that sending image requests to the Dall-e server is not as reliable as running images locally in SD. If the server gets overtaxed, or hiccups you may not get back all the images you requested. This Dall-e node will handle OpenAI server errors gracefully and allow your batch to continue to completion, but sometimes you may get back fewer images than you requested. If you keep the 'troubleshooting' output connected it will report any errors and let you know how many images were processed vs how many you requested.\n\n✦ seed: This works just like a seed in a KSampler except that it doesn't affect a latent or the image. It's simply there for you to set to: 'randomize' or 'increment' if you want Dall-e to run with every Queue, or to 'fixed' if you only want Dall-e to run once per prompt or setting. The Dall_e API doesn't actually pass seed values. This can also be controlled by the 'Global Seed' from the Inspire Pack. \n\n✦ Number_of_Tries: The number of attempts the node will make to try and connect and/or generate an image until successful. This Dall-e node will make the indicated number of attempts for each item in your batch if necessary. \n\n***************\n\n✦ troubleshooting output: Hook this output up to a text display node to see any INFO/WARNING/ERROR data that's generated during this node's run.\n\n✦ Dalle_e_prompt: The prompt that Dall-e 3 generates from your prompt. This is the prompt that actually gets passed to the image model. Hook up a text display node to see it.", + "adv_prompt_help": "• Advanced Prompt Enhancer (APE) uses AI Models to generate text output from any combination of: Instruction, Example_or_Context, Image and Prompt you provide. No API key is needed for Open source Models. This node can use various remote services and models, ChatGPT, Groq, OpenRouter, Sambanova and Anthropic Claude if you have an API key and have stored it in an environment variable (see GitHub ReadMe file). With or without a key it can also connect to various local apps and models e.g.: LM Studio, Oobabooga, Koboldcpp, etc.\n\n• image input: Advanced Prompt Enhancer can send image data (in the form of a b64 image file) to AI vision capable models. If you're sending an image to an AI model be sure both the model and the app or remote service have vision capabilities and can handle image files.\n\n• Examples_or_Context: APE can send example(s) and/or context along with your instructions to the LLM. Examples and Context *always* need to be in the form of: User input, then the delimiter, followed by the model's response. Delimited text entered in this field will automatically create alternatating input to the model for each delimited segment using this pattern. If you want to explicitly tag your text as being user or model input you can preface each delimited segment with <> or <>}. (There's an workflow file: 'How_To_Use_Examples.png' in the 'Example_Worflows' folder with details about using the Examples_or_Context input.)\n\n•Context (output): The 'Context' output is an accumulation of the 'Examples_or_Context' input plus the current 'Prompt' and 'LLM_response'. It can be fed directly into the 'Examples_or_Context' input of a second APE node. Before passing this information between nodes, make sure all the Context linked nodes have the same 'example_delimiter' setting. Each node linked in this way will accumulate all of the conversations of the nodes before it.\n\n• API Keys: API keys need to be kept in environment variables. The Environment Variable names that Advanced Prompt Enhancer looks for are: ✦ChatGPT: OPENAI_API_KEY or OAI_KEY; ✦Groq: GROQ_API_KEY; ✦Anthropic: ANTHROPIC_API_KEY; ✦OpenRouter and other remote serivces: LLM_KEY. Find instructions on how to create the Enviroment Variable here: https://github.com/glibsonoran/Plush-for-ComfyUI?tab=readme-ov-file#requirements . \n\n**************\n\n• AI_service: This indicates the type of AI service and connection you're going to send your data to. If you're using an AI Service that ends in '(URL)' you'll need to provide a valid URL in the LLM_URL field near the bottom of the node. If you're using 'Oobabooga API' make sure you read the LLM_URL help below. 'Direct Web Connection (URL)' uses a web POST action rather than the OpenAI API Object to communicate with the local or remote AI server, typically this requires an endpoint that has a 'v1/chat/completions' path in the URL. For Example: 'https://openrouter.ai/api/v1/chat/completions'. 'Web Connection Simplified Data (URL)' also uses a web POST action and presents a simplified data structure. Try this if the other AI service methods don't work, it will also require a: 'v1/chat/completions' path. 'OpenAI API Connection (URL)' on the other hand will only require a '/v1' path. For example: 'https://openrouter.ai/api/v1' \n\n• GPTmodel: This field only applies when the LLM field is set to 'ChatGPT'. Select the specific OpenAI ChatGPT model you want to use. If you're inputting an image, make sure the model you choose is vision capable.\n\n• Groq_model: This only applies when you select 'Groq' in the AI_service field. Choose the Groq model you want to use. \n\n• Anthropic_model: This only applies when you select 'Anthropic' from the AI_service field. Choose the Anthopic model you want to use. \n\n• Ollama_model: This will display the model(s) currently loaded in the Ollama front end. In order for models to show up in the drop down Ollama will have to be running with the models you intend to use loaded *before* starting ComfyUI. Note that APE looks for the standard url: http://localhost:11434/api/tags when retrieving the model names. If you've setup Ollama with another url (e.g. different port), you'll need to modify the 'urls.json' file. \n\n• Optional_model: This is a list of models extracted from the text file: '/custom_nodes/Plush-for-ComfyUI/Opt_models.txt'. This is a user configurable file that's initially empty. It's meant to hold model names for unique remote or local AI services that require a model name to be included with the inference request. These model names only apply to AI_Services that end in '(URL)'. If you enter or remove model names from this file, the changes will only show up after you reboot ComfyUI. Instructions on how to enter these model names is in the comments header of the 'Opt_models.txt' text file. \n\n• creative_latitude: (Temperature) This will set how strictly the LLM adheres to common word relationships and how closely it will follow your instruction and prompt. Setting this value higher allows more creative freedom in interpreting your input and generating its ouptput.\n\n• tokens: The maximum number of tokens that the LLM can use in processing your prompt and return text. This is not the number of tokens it 'will' use, it's the number available that it 'can' use.\n\n• seed: This is a pseudo or mock seed, it has no effect on the text generated, and it's not passed to the LLM. It's used here solely to control when the node will run. It works the same as a KSampler, set it to 'fixed' if you want the node to run only once each time you change your inputs, set it to random or increment/decrement if you want it run with each Queue.\n\n• example_delimiter: You can provide multiple examples or context to the LLM. Providing multiple examples for a given instruction is a type of 'Few Shot Prompting', which can be effective with some LLM's. This field indicates how the node will distinguish each separate example, each separate example or context item will be presented as originating from the User then the Model alternating in that order for as many as you enter. You can choose to separate your examples with a pipe '|' character, two newlines (i.e.: carriage returns) or two colons '::', these are called delimiters and they denote where these separations will occur.\n\n• LLM_URL: When using an LLM other than ChatGPT, Anthropic or Groq you'll need to provide a URL in this field. Typically the AI application you're using (e.g. LM Studio, Oobabooga, OpenRouter), will indicate the URL to use either: After you startup its server if it's a local app, or on a documents or help web page if it's a remote server. For local apps like LM Stuido, it may be in the terminal output or in the UI. Some local AI apps will specify that a particular URL is OpenAI compatible, if so this is the one you want to use. Typically the URLs for local apps have this general format: http://localhost:5001/v1 where '5001' is the port and 'localhost' is interchangable with '127.0.0.1'. If you're using the Oobabooga API or 'Direct Web Connection (URL)' selection your url will need to have /chat/completions appended as part of the url: http://127.0.0.1:5000/v1/chat/completions. \n\n• Number_of_Tries: The number of times Advanced Prompt Enhancer will attempt to connect and generate output from the AI Service until successful. If after the indicated number of tries the process is still not successful, it will fail and display the error information from the 'troubleshooting' output. seed:\n\n**************\n\n• Use the troubleshooting output if you have issues with model connections, or if you want to see exactly which model was used to produce your output (some ChatGPT model names are actually only pointers to the latest specific model in that category) and how many tokens were used.", "tagger_help": "• Tagger adds tags to the beginning, middle or end of a text block. Tagger can be used whenever you want to add text that needs to appear exactly as written. \n\n**************\n\n• Beginning_tags: The text (tags) you want to appear at the very beginning of the input text block. It will preface all other text in the block. \n\n• Middle_tags: The text (tags) you want to appear in the middle of the text block. These tags will always appear immediately after a comma or period. \n\n• Prefer_middle_tag_after_period: You can indicate a preference for the tags to follow a period by clicking this button. Otherwise the tags may follow a period or a comma whichever is closest to the middle of the text. \n\n• End_tags: Tags that will be appended to the end of the input text block.\n\n• Examples: Beginning_tags: '[An Abstract Painting:| Digital Art:]', Middle_tags: '(Big Black Hat:1.4)', End_tags: 'In the style of Piet Mondrian' ", - "add_param_help": "• BE AWARE THAT CERTAIN PARAMETERS MAY NOT WORK WITH ALL MODELS OR SERVICES. You should display Advanced Prompt Enhancer's 'Troubleshooting' output when testing parameters on a model so you can quickly diagnose issues. Additional Parameters allows you to add parameters to your LLM completions request using Advanced Prompt Enhancer (APE). These parameters affect the way the LLM handles your input data. You're probably already familiar with 'temperature' (which is shown as 'creative_latitude' in APE), this node allows you to add other parameters that aren't available in the APE user interface. If you want to see an example of how this node is used I have an example workflow in '/custom_nodes/Plush-For-ComfyUI/Example_Workflows/How_to_use_additionalParameters.png'. You can find a list of parameters for OpenAI models at this address: https://platform.openai.com/docs/api-reference/chat \n***************\n\n• The 'Add_Parameter(s)' input and output: These handle LIST data and will only connect to other nodes that can handle LIST data. The 'Add_Parameter' input on APE is compatible with these input/outputs. Plus 'Additional Parameter' nodes can be daisy chained, the result being that you can provide several different parameters at once.\n**************** \n\n• Parameter type: This dropdown list contains three elements: 'none', 'OpenAI JSON Format' and 'User Defined'. 'User Defined' is the only mode that will process the other fields in the node. If you select 'User Defined' you'll need to fill out the additional fields below. If you select 'none' then that particular node will provide no output, even if the Param_Name and Param_Value fields are filled out. If a node is set to none in a set of Daisy chained nodes, it will just pass the information from the other nodes along without contributing anything. 'OpenAI JSON Format' is a special format that tells the LLM to create its output as a JSON. Along with selecting this you must mention 'JSON' in the prompt and the prompt must instruct the node as to the structure of the JSON. This parameter only works with OpenAI models ‘chatgpt-4’ and later.\n\n✦ Param_Name: This field is only accessed when Parameter-type is set to: 'User Defined'. This is the name of the parameter to be added, see the link in the beginning of this help text for a web address where you can see a list of available parameters for OpenAI. \n\n✦ Param_Value: This field is only accessed when Parameter_type is set to: 'User Defined'. This is the value that will be passed to the parameter. Parameter values are passed through a data type inference algorithm to convert them to the proper data type, if you want to ensure your Param_Value will be passed as a string, enclose it in double quotes. \n\n✦ Is_JSON: This field is only accessed when Parameter_type is set to: 'User Defined'. If the parameter value needs to be in the form of a JSON, check this box. Additional Parameters will then validate the input and convert it to a JSON.", "add_params_help": "• BE AWARE THAT CERTAIN PARAMETERS MAY NOT WORK WITH ALL MODELS OR SERVICES. You should display Advanced Prompt Enhancer's 'Troubleshooting' output when testing parameters on a model so you can quickly diagnose issues. Add Parameters allows you to add parameters to your LLM completions request using Advanced Prompt Enhancer (APE). These parameters affect the way the LLM handles your input data. You're probably already familiar with 'temperature' (which is shown as 'creative_latitude' in APE), this node allows you to add other parameters that aren't available in the APE user interface. If you want to see an example of how this node is used I have an example workflow in '/custom_nodes/Plush-For-ComfyUI/Example_Workflows/How_to_use_addParameters.png'. You can find a list of parameters for OpenAI models at this address: https://platform.openai.com/docs/api-reference/chat \n***************\n\n• The 'Add_Parameter(s)' output: This output provides LIST data and will only connect to other nodes that can handle LIST data. The 'Add_Parameter' input on APE is compatible with this output. \n**************** \n\n• Parameter: List your parameters in this text area using the format 'parameter name::value' e.g. 'top_p::0.9' make sure to place two colons between the parameter name and the value. Place each parameter::value pair on a separate line. You don't need commas or semicolons between lines, just a newline. You can add comments in this text area by prefacing each comment line with a '#' character, e.g.:'# my comment'.\n\n✦ Save_to_file: Check this box if you want to save your parameter list and comments to a text file. The file will be placed in: [...ComfyUI/output/PlushFiles]. You'll need to provide a file name also. \n\n✦ File_name: Enter the name of the file you want to save. The file name will begin with the text you provide and also have a unique identifier added. The program automatically adds the .txt extension.", "extract_json_help": "• Extract JSON lets you extract values from a string JSON that correspond to the JSON keys you enter. If there are duplicate keys in the JSON, the multiple values will be extracted in a list, e.g.: “[‘value1’, ‘value2’]”. If you want to see an example of how this node is used I have an example workflow in '/custom_nodes/Plush-For-ComfyUI/Example_Workflows/How_to_use_additionalParameters.png'. \n***************\n\n✦ The ‘json_string’ input accepts text (string) data that is properly formatted as a JSON. JSON objects or dictionaries will not work as input for this node. If you want to validate that your JSON string is properly formed I recommend using this website: https://jsonformatter.org. Only text(string) data is output from this node. If the output data is contained in a list, per the earlier example, the list will be presented as text (string). The ‘JSON_Obj’ output will not necessarily produce the same JSON that was input. Instead it is a JSON the node assembles that holds only the data associated with the keys you entered. This output is in the form of a JSON Object/dictionary, not text (string).. \n**************** \n\n✦ key_1..2..3 etc: These are the keys you want to retrieve value data from. The node won’t return the keys themselves (except in the JSON_Obj output). It will return the values that are associated with the keys. It’s like if you were accessing an employee database record and you looked up the ‘name’. ‘Name’ would be the key and the employee’s actual first and last name would be the value. The keys correspond numerically to the outputs (e.g. key_1 will output data to string_1, etc.).", "type_convert_help": "• Converts a string value to its inferred type or types.\n\n******************\n\n✦ Cross_reference_types: When set to True the node will infer the primary data type and also offer equivalent values in other data types. For example: If you provide the node the string value: '1', it will infer the primary data type as Integer. However if Cross_reference_types is set to True it will also provide the Float value: 1.0 and the Boolean value: True, all of which are valid Python represntations of 1. If you were to provide the string value '1.6' with Cross_reference_types set to True, the node would infer the primary data type as Float and also provide the Integer 2, the closest round to the Float value. If Cross_reference_types is set to False, the node will only provide the primary inferred data type. " diff --git a/style_prompt.py b/style_prompt.py index 92e3b10..2c7bce6 100644 --- a/style_prompt.py +++ b/style_prompt.py @@ -398,11 +398,12 @@ class AI_Chooser: "ChatGPT": RequestMode.OPENAI, "Groq": RequestMode.GROQ, "Anthropic": RequestMode.CLAUDE, - "LM_Studio": RequestMode.LMSTUDIO, - "Local app (URL)": RequestMode.OPENSOURCE, - "OpenAI compatible http POST": RequestMode.OPENSOURCE, - "http POST Simplified Data": RequestMode.OSSIMPLE, - "Oobabooga API-URL": RequestMode.OOBABOOGA + "OpenAI API Connection (URL)": RequestMode.OPENSOURCE, + "Direct Web Connection (URL)": RequestMode.OPENSOURCE, + "LM_Studio (URL)": RequestMode.OPENSOURCE, + "Ollama (URL)": RequestMode.OPENSOURCE, + "Web Connection Simplified Data (URL)": RequestMode.OSSIMPLE, + "Oobabooga API (URL)": RequestMode.OOBABOOGA } return mode_map.get(user_selection) @@ -871,17 +872,18 @@ class AdvPromptEnhancer: #refresh the ui after the initial load. return { "required": { - "AI_service": (["ChatGPT", "Groq", "Anthropic", "LM_Studio (URL)", "Ollama (URL)","Local app (URL)", "OpenAI compatible http POST (URL)", "http POST Simplified Data (URL)", "Oobabooga API (URL)"], {"default": "Groq"}), + "AI_service": (["ChatGPT", "Groq", "Anthropic", "LM_Studio (URL)", "Ollama (URL)","OpenAI API Connection (URL)", "Direct Web Connection (URL)", "Web Connection Simplified Data (URL)", "Oobabooga API (URL)"], {"default": "Groq", "tooltip": "Choose connection type, connections ending with '(URL)' require a URL to be entered"}), "ChatGPT_model": (cFig.get_chat_models(True,gptfilter), {"default": ""}), "Groq_model": (cFig.get_groq_models(True), {"default": ""}), "Anthropic_model": (cFig.get_claude_models(True), {"default": ""}), "Ollama_model": (cFig.get_ollama_models(True), {"default": ""}), - "Optional_model": (cFig.get_optional_models(True), {"default": ""}), - "creative_latitude" : ("FLOAT", {"max": 1.901, "min": 0.1, "step": 0.1, "display": "number", "round": 0.1, "default": 0.7}), + "Optional_model": (cFig.get_optional_models(True), {"default": "", "tooltip": "Enter these in text file: opt_models.txt"}), + "creative_latitude" : ("FLOAT", {"max": 1.901, "min": 0.1, "step": 0.1, "display": "number", "round": 0.1, "default": 0.7, "tooltip": "temperature"}), "tokens" : ("INT", {"max": 8000, "min": 20, "step": 10, "default": 500, "display": "number"}), "seed": ("INT", {"default": 9, "min": 0, "max": 0xffffffffffffffff}), "examples_delimiter":(["Pipe |", "Two newlines", "Two colons ::"], {"default": "Two newlines"}), - "LLM_URL": ("STRING",{"default": cFig.lm_url}) + "LLM_URL": ("STRING",{"default": cFig.lm_url, "tooltip": "Enter the url for your service here when using connections that end with: (URL)"}), + "Number_of_Tries": (["1","2","3","4","5","default"], {"default": "default"}) }, @@ -908,7 +910,7 @@ class AdvPromptEnhancer: CATEGORY = "Plush/Prompt" def gogo(self, AI_service, ChatGPT_model, Groq_model, Anthropic_model, Ollama_model, Optional_model, creative_latitude, tokens, seed, examples_delimiter, - Add_Parameter=None, LLM_URL:str="", Instruction:str="", Prompt:str = "", Examples_or_Context:str ="", image=None, unique_id=None): + Number_of_Tries:str="", Add_Parameter=None, LLM_URL:str="", Instruction:str="", Prompt:str = "", Examples_or_Context:str ="", image=None, unique_id=None): if unique_id: self.trbl.reset("Advanced Prompt Enhancer, Node #"+unique_id) @@ -971,15 +973,16 @@ class AdvPromptEnhancer: "image": image, "example_list": example_list, "add_params": Add_Parameter, + "tries": Number_of_Tries } context_output = "" ctx_delimiter = "\n" + delimiter +"\n" context_output = (Examples + ctx_delimiter if Examples else "") + Prompt + ctx_delimiter - if AI_service == 'Local app (URL)' or AI_service == "Groq" or AI_service == "Ollama (URL)": + if AI_service == 'OpenAI API Connection (URL)' or AI_service == "Groq" or AI_service == "Ollama (URL)": - if AI_service == 'Local app (URL)': + if AI_service == 'OpenAI API Connection (URL)': self.cFig.lm_request_mode = RequestMode.OPENSOURCE elif AI_service == "Groq": self.cFig.lm_request_mode = RequestMode.GROQ @@ -988,7 +991,7 @@ class AdvPromptEnhancer: self.cFig.lm_request_mode = RequestMode.OLLAMA if not LLM_URL: - self.j_mngr.log_events("'Local app (URL)' specified, but no URL provided or URL is invalid. Enter a valid URL", + self.j_mngr.log_events("'OpenAI API Connection (URL)' specified, but no URL provided or URL is invalid. Enter a valid URL", TroubleSgltn.Severity.WARNING, True) return(llm_result,"", _help, self.trbl.get_troubles()) @@ -1025,9 +1028,9 @@ class AdvPromptEnhancer: return(claude_result, context_output, _help, self.trbl.get_troubles()) - if AI_service == "OpenAI compatible http POST (URL)" or AI_service == "LM_Studio (URL)": + if AI_service == "Direct Web Connection (URL)" or AI_service == "LM_Studio (URL)": if not LLM_URL: - self.j_mngr.log_events("'OpenAI compatible http POST' specified, but no URL provided or URL is invalid. Enter a valid URL", + self.j_mngr.log_events("'Direct Web Connection (URL)' specified, but no URL provided or URL is invalid. Enter a valid URL", TroubleSgltn.Severity.WARNING, True) return(llm_result, "", _help, self.trbl.get_troubles()) @@ -1045,9 +1048,9 @@ class AdvPromptEnhancer: return(llm_result, context_output, _help, self.trbl.get_troubles()) - if AI_service == "http POST Simplified Data (URL)": + if AI_service == "Web Connection Simplified Data (URL)": if not LLM_URL: - self.j_mngr.log_events("'http POST Simplified Data' specified, but no URL provided or URL is invalid. Enter a valid URL", + self.j_mngr.log_events("'Web Connection Simplified Data (URL)' specified, but no URL provided or URL is invalid. Enter a valid URL", TroubleSgltn.Severity.WARNING, True) return(llm_result, "", _help, self.trbl.get_troubles()) @@ -1215,7 +1218,8 @@ class DalleImage: "image_quality": (["standard", "hd"], {"default": "hd"} ), "style": (["vivid", "natural"], {"default": "natural"} ), "batch_size": ("INT", {"max": 8, "min": 1, "step": 1, "default": 1, "display": "number"}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}) + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "Number_of_Tries": (["1","2","3","4","5","default"], {"default": "default"}) }, "hidden": { "unique_id": "UNIQUE_ID", @@ -1231,7 +1235,7 @@ class DalleImage: CATEGORY = "Plush/Image_Gen" - def gogo(self, GPTmodel, prompt, image_size, image_quality, style, batch_size, seed, unique_id=None): + def gogo(self, GPTmodel, prompt, image_size, image_quality, style, batch_size, seed, Number_of_Tries:str, unique_id=None): if unique_id: self.trbl.reset('Dall-e Image, Node #' + unique_id) @@ -1240,6 +1244,7 @@ class DalleImage: _help = self.help_data.dalle_help + self.cFig.lm_request_mode = RequestMode.DALLE self.ctx.request = rqst.dall_e_request() kwargs = { "model": GPTmodel, "prompt": prompt, @@ -1247,6 +1252,7 @@ class DalleImage: "image_quality": image_quality, "style": style, "batch_size": batch_size, + "tries": Number_of_Tries } batched_images, revised_prompt = self.ctx.execute_request(**kwargs) diff --git a/update.json b/update.json index 4498405..e7e35b3 100644 --- a/update.json +++ b/update.json @@ -3,6 +3,7 @@ "style":[ "Portrait Photograph", "Fashion Photograph", + "Impasto Oil Painting", "Street Photography", "Documentary Photography", "Still Life Photograph",