Files
glibsonoran-Plush-for-ComfyUI/api_requests.py
T
2024-07-06 13:29:44 -07:00

956 lines
40 KiB
Python

from abc import ABC, abstractmethod
import torch
import time
import re
import requests
from urllib.parse import urlparse, urlunparse
import openai
import anthropic
from .mng_json import json_manager, TroubleSgltn
from .fetch_models import RequestMode
class ImportedSgltn:
"""
This class is temporary to prevent circular imports
"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(ImportedSgltn, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if not self._initialized: #pylint: disable=access-member-before-definition
self._initialized = True
self._cfig = None
self._dalle = None
self._request_mode = None
self.get_imports()
def get_imports(self):
# 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):
if self._cfig is None:
self.get_imports()
return self._cfig()
@property
def dalle(self):
if self._dalle is None:
self.get_imports()
return self._dalle()
#Begin Strategy Pattern
class Request(ABC):
def __init__(self):
self.imps = ImportedSgltn()
self.utils = request_utils()
self.cFig = self.imps.cfig
self.mode = RequestMode
self.dalle = self.imps.dalle
self.j_mngr = json_manager()
@abstractmethod
def request_completion(self, **kwargs) -> None:
pass
class oai_object_request(Request): #Concrete class
def request_completion(self, **kwargs):
GPTmodel = kwargs.get('model')
creative_latitude = kwargs.get('creative_latitude', 0.7)
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', [])
request_type = self.cFig.lm_request_mode
response = None
CGPT_response = ""
file += file.strip()
if request_type == self.mode.OPENSOURCE:
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
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
#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 = []
#Use basic data structure if there is no 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:
# 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
params = {
"model": GPTmodel,
"messages": messages,
"temperature": creative_latitude,
"max_tokens": tokens
}
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:
self.j_mngr.log_events(f"Server RATE LIMIT error {e.status_code}: {e.response} {e.body['message'] if e.body else ''}",
TroubleSgltn.Severity.ERROR,
True)
except openai.APIStatusError as e:
self.j_mngr.log_events(f"Server STATUS error {e.status_code}: {e.body['message'] if e.body else ''}. File may be too large.",
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', [])
request_type = self.cFig.lm_request_mode
response = None
CGPT_response = ""
#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
}
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
response = None
CGPT_response = ""
url = self.utils.validate_and_correct_url(url) #validate v1/chat/completions path
#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
}
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 claude_request(Request):
def request_completion(self, **kwargs):
claude_model = kwargs.get('model')
creative_latitude = kwargs.get('creative_latitude', 0.7)
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', [])
request_type = self.cFig.lm_request_mode
response = None
claude_response = ""
file += file.strip()
if request_type == self.mode.CLAUDE:
client = self.cFig.anthropic_client
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
#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 = []
messages = self.utils.build_data_claude(prompt, example_list, image)
if not prompt and not image and not instruction:
# 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
params = {
"model": claude_model,
"messages": messages,
"temperature": creative_latitude,
"system": instruction,
"max_tokens": tokens
}
try:
response = client.messages.create(**params)
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:
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 dall_e_request(Request):
def __init__(self):
super().__init__() # Ensures common setup from Request
self.trbl = TroubleSgltn()
def request_completion(self, **kwargs)->tuple[torch.Tensor, str]:
GPTmodel = kwargs.get('model')
prompt = kwargs.get('prompt')
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)
have_rev_prompt = False
images_list = []
for _ in range(batch_size):
try:
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:
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)
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)
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)
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.trbl.pop_header()
return(batched_images, revised_prompt)
class request_context:
def __init__(self)-> None:
self._request = None
self.j_mngr = json_manager()
@property
def request(self)-> Request:
return self._request
@request.setter
def request(self, request:Request)-> None:
self._request = request
def execute_request(self, **kwargs):
if self._request is not None:
return self._request.request_completion(**kwargs)
self.j_mngr.log_events("No request strategy object was set",
TroubleSgltn.Severity.ERROR,
True)
return None
class request_utils:
def __init__(self)-> None:
self.j_mngr = json_manager()
def build_data_multi(self, prompt:str, instruction:str="", examples:list=None, image:str=None):
"""
Builds a list of message dicts, aggregating 'role:user' content into a list under 'content' key.
- image: Base64-encoded string or None. If string, included as 'image_url' type content.
- prompt: String to be included as 'text' type content under 'user' role.
- examples: List of additional example dicts to be included.
- instruction: Instruction string to be included under 'system' role.
"""
messages = []
user_role = {"role": "user", "content": None}
user_content = []
if examples is None:
examples = []
if image and isinstance(image,str):
image_url = f"data:image/jpeg;base64,{image}"
user_content.append({"type": "image_url", "image_url": {"url":image_url}})
elif image:
self.j_mngr.log_events("Image file is invalid. Image will be disregarded in the generated output.",
TroubleSgltn.Severity.WARNING,
True)
if prompt:
user_content.append({"type": "text", "text": prompt})
user_role['content'] = user_content
#Structure dicts & lists in order: System/User/Assitant
if instruction:
messages.append({"role": "system", "content": instruction})
messages.append(user_role)
if examples:
messages.extend(examples)
return messages
def build_data_basic(self, prompt:str, examples:list=None, instruction:str=""):
"""
Builds a list of message dicts, presenting each 'role:user' item in its own dict.
- prompt: String to be included as 'text' type content under 'user' role.
- examples: List of additional example dicts to be included.
- instruction: Instruction string to be included under 'system' role.
"""
messages = []
if examples is None:
examples = []
if instruction:
messages.append({"role": "system", "content": instruction})
if prompt:
messages.append({"role": "user", "content": prompt})
if examples:
messages.extend(examples)
return messages
def build_data_ooba(self, prompt:str, examples:list=None, instruction:str="")-> list:
"""
Builds a list of message dicts, presenting each 'role:user' item in its own dict.
Since Oobabooga's system message is broken it includes it in the prompt
- prompt: String to be included as 'text' type content under 'user' role.
- examples: List of additional example dicts to be included.
- instruction: Instruction string to be included under 'system' role.
"""
messages = []
ooba_prompt = f"INSTRUCTION: {instruction} \nPROMPT: {prompt}"
if examples is None:
examples = []
if ooba_prompt:
messages.append({"role": "user", "content": ooba_prompt})
if examples:
messages.extend(examples)
return messages
def build_data_claude(self, prompt:str, examples:list=None, image:str=None)-> list:
"""
Builds a list of message dicts, aggregating 'role:user' content into a list under 'content' key.
- image: Base64-encoded string or None. If string, included as 'image_url' type content.
- prompt: String to be included as 'text' type content under 'user' role.
- examples: List of additional example dicts to be included.
"""
messages = []
user_role = {"role": "user", "content": None}
user_content = []
if examples is None:
examples = []
if image and isinstance(image,str):
user_content.append({"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": image}})
elif image:
self.j_mngr.log_events("Image file is invalid. Image will be disregarded in the generated output.",
TroubleSgltn.Severity.WARNING,
True)
if prompt:
user_content.append({"type": "text", "text": prompt})
user_role['content'] = user_content
messages.append(user_role)
if examples:
messages.extend(examples)
return messages
def build_web_header(self, key:str=""):
if key:
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {key}"
}
else:
headers = {
"Content-Type": "application/json"
}
return headers
def validate_and_correct_url(self, user_url:str, required_path:str='/v1/chat/completions'):
"""
Takes the user's url and make sure it has the correct path for the connection
args:
user_url (str): The url to be validated and corrected if necessary
required_path (str): The correct path
return:
A string with either the original url if it was correct or the corrected url if it wasn't
"""
corrected_url = ""
parsed_url = urlparse(user_url)
# Check if the path is the required_path
if not parsed_url.path == required_path:
corrected_url = urlunparse((parsed_url.scheme,
parsed_url.netloc,
required_path,
'',
'',
''))
else:
corrected_url = user_url
self.j_mngr.log_events(f"URL was validated and is being presented as: {corrected_url}",
TroubleSgltn.Severity.INFO,
True)
return corrected_url
def clean_response_text(self, text: str)-> str:
# Replace multiple newlines or carriage returns with a single one
cleaned_text = re.sub(r'\n+', '\n', text).strip()
return cleaned_text
@staticmethod
def parse_anthropic_error(e):
"""
Parses error information from an exception object.
Args:
e (Exception): The exception from which to parse the error information.
Returns:
str: A user-friendly error message.
"""
# Default error message
default_message = "An unknown error occurred"
# Check if the exception has a response attribute and it can be converted to JSON
if hasattr(e, 'response') and callable(getattr(e.response, 'json', None)):
try:
error_details = e.response.json()
# Navigate through the nested dictionary safely
return error_details.get('error', {}).get('message', default_message)
except ValueError:
# JSON decoding failed
return f"Failed to decode JSON from response: {e.response.text}"
except Exception as ex:
# Catch-all for any other issues that may arise
return f"Error processing the error response: {str(ex)}"
elif hasattr(e, 'message'):
return e.message
else:
return str(e)