Files
siliconflow-BizyAir/utils.py
lcolok dc2275dd99 Add SiliconCloud VLM API Node (#172)
* Refactor model fetching extension for SiliconCloud LLM and VLM APIs

* Add VLM API support

* Add image detail control to SiliconCloudVLMAPI
2024-10-21 10:57:47 +08:00

219 lines
6.2 KiB
Python

import base64
import json
import os
import pickle
import urllib.parse
import urllib.request
import zlib
from typing import List, Tuple, Union
import numpy as np
BIZYAIR_DEBUG = os.getenv("BIZYAIR_DEBUG", False)
def send_post_request(api_url, payload, headers):
"""
Sends a POST request to the specified API URL with the given payload and headers.
Args:
api_url (str): The URL of the API endpoint.
payload (dict): The payload to send in the POST request.
headers (dict): The headers to include in the POST request.
Raises:
Exception: If there is an error connecting to the server or the request fails.
"""
try:
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(api_url, data=data, headers=headers, method="POST")
with urllib.request.urlopen(req) as response:
response_data = response.read().decode("utf-8")
return response_data
except urllib.error.URLError as e:
if "Unauthorized" in str(e):
raise Exception(
"Key is invalid, please refer to https://cloud.siliconflow.cn to get the API key.\n"
"If you have the key, please click the 'BizyAir Key' button at the bottom right to set the key."
)
else:
raise Exception(
f"Failed to connect to the server: {e}, if you have no key, "
)
def serialize_and_encode(obj: Union[np.ndarray], compress=True) -> Tuple[str, bool]:
"""
Serializes a Python object, optionally compresses it, and then encodes it in base64.
Args:
obj: The Python object to serialize.
compress (bool): Whether to compress the serialized object using zlib. Default is True.
Returns:
str: The base64 encoded string of the serialized (and optionally compressed) object.
"""
serialized_obj = pickle.dumps(obj)
if compress:
serialized_obj = zlib.compress(serialized_obj)
if BIZYAIR_DEBUG:
print(
f"serialize_and_encode: size of bytes is {format_bytes(len(serialized_obj))}"
)
encoded_obj = base64.b64encode(serialized_obj).decode("utf-8")
if BIZYAIR_DEBUG:
print(
f"serialize_and_encode: size of base64 text is {format_bytes(len(serialized_obj))}"
)
return (encoded_obj, compress)
def decode_and_deserialize(response_text) -> np.ndarray:
if BIZYAIR_DEBUG:
print(
f"decode_and_deserialize: size of text is {format_bytes(len(response_text))}"
)
ret = json.loads(response_text)
if "result" in ret:
msg = json.loads(ret["result"])
else:
msg = ret
if msg["type"] not in (
"comfyair",
"bizyair",
): # DO NOT CHANGE THIS LINE: "comfyair" is the type from the server node
# TODO: change both server and client "comfyair" to "bizyair"
raise Exception(f"Unexpected response type: {msg}")
data = msg["data"]
tensor_bytes = base64.b64decode(data["payload"])
if data.get("is_compress", None):
tensor_bytes = zlib.decompress(tensor_bytes)
if BIZYAIR_DEBUG:
print(
f"decode_and_deserialize: size of bytes is {format_bytes(len(tensor_bytes))}"
)
deserialized_object = pickle.loads(tensor_bytes)
return deserialized_object
def format_bytes(num_bytes: int) -> str:
"""
Converts a number of bytes to a human-readable string with units (B, KB, or MB).
:param num_bytes: The number of bytes to convert.
:return: A string representing the number of bytes in a human-readable format.
"""
if num_bytes < 1024:
return f"{num_bytes} B"
elif num_bytes < 1024 * 1024:
return f"{num_bytes / 1024:.2f} KB"
else:
return f"{num_bytes / (1024 * 1024):.2f} MB"
def get_api_key():
from .auth import API_KEY
return API_KEY
def get_llm_response(
model: str,
system_prompt: str,
user_prompt: str,
max_tokens: int = 1024,
temperature: float = 0.7,
):
api_url = "https://api.siliconflow.cn/v1/chat/completions"
API_KEY = get_api_key()
headers = {
"accept": "application/json",
"content-type": "application/json",
"Authorization": f"Bearer {API_KEY}",
}
payload = {
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": 0.9,
"top_k": 50,
"stream": False,
"n": 1,
}
response = send_post_request(api_url, headers=headers, payload=payload)
return response
def get_vlm_response(
model: str,
system_prompt: str,
user_prompt: str,
base64_images: List[str],
max_tokens: int = 1024,
temperature: float = 0.7,
detail: str = "auto",
):
api_url = "https://api.siliconflow.cn/v1/chat/completions"
API_KEY = get_api_key()
headers = {
"accept": "application/json",
"content-type": "application/json",
"Authorization": f"Bearer {API_KEY}",
}
messages = [
{
"role": "user",
"content": [{"type": "text", "text": system_prompt}],
}, # 此方法皆适用于两种 VL 模型
# {
# "role": "system",
# "content": system_prompt,
# }, # role 为 "system" 的这种方式只适用于 QwenVL 系列模型,并不适用于 InternVL 系列模型
]
user_content = []
for base64_image in base64_images:
user_content.append(
{
"type": "image_url",
"image_url": {
"url": f"data:image/webp;base64,{base64_image}",
"detail": detail,
},
}
)
user_content.append({"type": "text", "text": user_prompt})
messages.append({"role": "user", "content": user_content})
payload = {
"model": model,
"messages": messages,
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": 0.9,
"top_k": 50,
"stream": False,
"n": 1,
}
response = send_post_request(api_url, headers=headers, payload=payload)
return response