872 lines
34 KiB
Python
872 lines
34 KiB
Python
import random
|
|
import re
|
|
import json
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
import httpx
|
|
import numpy as np
|
|
import base64
|
|
from io import BytesIO
|
|
from server import PromptServer
|
|
from aiohttp import web
|
|
from pprint import pprint
|
|
from PIL import Image
|
|
from PIL.PngImagePlugin import PngInfo
|
|
import os
|
|
|
|
|
|
# Helper function to format conversation history for readability
|
|
def format_conversation_history(messages: List[Dict[str, Any]]) -> str:
|
|
formatted_history = []
|
|
for message in messages:
|
|
role = message.get("role", "unknown").capitalize()
|
|
content = message.get("content", "")
|
|
|
|
# Handle content that might be a list (e.g., for vision models)
|
|
if isinstance(content, list):
|
|
text_parts = [part["text"] for part in content if part.get("type") == "text"]
|
|
content = "\n".join(text_parts) + " (contains images)" if text_parts else "(contains images)"
|
|
|
|
formatted_history.append(f"--- {role} ---\n{content}\n")
|
|
return "\n".join(formatted_history).strip()
|
|
|
|
|
|
# This endpoint will fetch models from LM Studio's API
|
|
@PromptServer.instance.routes.post("/lmstudio/get_models")
|
|
async def get_models_endpoint(request):
|
|
data = await request.json()
|
|
|
|
url = data.get("url")
|
|
|
|
try:
|
|
# LM Studio's models endpoint is typically /v1/models
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(f"{url}/v1/models")
|
|
response.raise_for_status() # Raise an exception for HTTP errors
|
|
|
|
models_data = response.json().get('data', [])
|
|
# LM Studio returns models with an 'id' field
|
|
models = [model['id'] for model in models_data if 'id' in model]
|
|
return web.json_response(models)
|
|
except httpx.RequestError as e:
|
|
print(f"Error fetching models from LM Studio: {e}")
|
|
return web.json_response({"error": f"Could not connect to LM Studio: {e}"}, status=500)
|
|
except json.JSONDecodeError:
|
|
print("Error decoding JSON response from LM Studio.")
|
|
return web.json_response({"error": "Invalid JSON response from LM Studio"}, status=500)
|
|
except Exception as e:
|
|
print(f"An unexpected error occurred: {e}")
|
|
return web.json_response({"error": f"An unexpected error occurred: {e}"}, status=500)
|
|
|
|
|
|
class LMStudioVision:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
seed = random.randint(1, 2 ** 31)
|
|
return {
|
|
"required": {
|
|
"images": ("IMAGE",),
|
|
"query": ("STRING", {
|
|
"multiline": True,
|
|
"default": "describe the image"
|
|
}),
|
|
"debug": (["enable", "disable"],),
|
|
"url": ("STRING", {
|
|
"multiline": False,
|
|
"default": "http://localhost:1234" # Default LM Studio URL
|
|
}),
|
|
"model": ((), {}),
|
|
"temperature": ("FLOAT", {"default": 0.8, "min": 0, "max": 1, "step": 0.05}),
|
|
"seed": ("INT", {"default": seed, "min": 0, "max": 2 ** 31, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("description", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_vision"
|
|
CATEGORY = "LM Studio"
|
|
|
|
def lmstudio_vision(self, images, query, debug, url, model, temperature, seed, previous_conv=None):
|
|
messages: List[Dict[str, Any]] = []
|
|
|
|
if previous_conv:
|
|
try:
|
|
history = json.loads(previous_conv)
|
|
if isinstance(history, list):
|
|
messages.extend(history)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: Could not parse previous_conv JSON: {previous_conv}")
|
|
|
|
images_b64 = []
|
|
for (batch_number, image) in enumerate(images):
|
|
i = 255. * image.cpu().numpy()
|
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
|
buffered = BytesIO()
|
|
img.save(buffered, format="PNG")
|
|
img_bytes = base64.b64encode(buffered.getvalue())
|
|
images_b64.append(str(img_bytes, 'utf-8'))
|
|
|
|
user_content = [{"type": "text", "text": query}] + [{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}} for img_b64 in images_b64]
|
|
messages.append({"role": "user", "content": user_content})
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
"seed": seed,
|
|
}
|
|
|
|
if debug == "enable":
|
|
print(f"""[LM Studio Vision]
|
|
request query params:
|
|
|
|
- query: {query}
|
|
- url: {url}
|
|
- model: {model}
|
|
- temperature: {temperature}
|
|
- seed: {seed}
|
|
- messages: {messages}
|
|
""")
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(f"{url}/v1/chat/completions", json=payload, timeout=600.0)
|
|
response.raise_for_status()
|
|
|
|
lmstudio_response = response.json()
|
|
|
|
if debug == "enable":
|
|
print("[LM Studio Vision]\nResponse:\n")
|
|
pprint(lmstudio_response)
|
|
|
|
description = lmstudio_response['choices'][0]['message']['content']
|
|
messages.append({"role": "assistant", "content": description}) # Add assistant response to history
|
|
|
|
return (description, json.dumps(messages), format_conversation_history(messages),) # Return raw JSON and formatted history
|
|
except httpx.RequestError as e:
|
|
raise Exception(f"LM Studio Vision API request failed: {e}")
|
|
except json.JSONDecodeError:
|
|
raise Exception("LM Studio Vision API returned invalid JSON.")
|
|
except KeyError as e:
|
|
raise Exception(f"LM Studio Vision API response missing expected key: {e}. Full response: {lmstudio_response}")
|
|
|
|
|
|
class LMStudioGenerate:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
seed = random.randint(1, 2 ** 31)
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {
|
|
"multiline": True,
|
|
"default": "What is Art?"
|
|
}),
|
|
"debug": (["enable", "disable"],),
|
|
"url": ("STRING", {
|
|
"multiline": False,
|
|
"default": "http://localhost:1234" # Default LM Studio URL
|
|
}),
|
|
"model": ((), {}),
|
|
"temperature": ("FLOAT", {"default": 0.8, "min": 0, "max": 1, "step": 0.05}),
|
|
"seed": ("INT", {"default": seed, "min": 0, "max": 2 ** 31, "step": 1}),
|
|
"filter_thinking": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("response", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_generate"
|
|
CATEGORY = "LM Studio"
|
|
|
|
def lmstudio_generate(self, prompt, debug, url, model, temperature, seed, filter_thinking, previous_conv=None):
|
|
messages: List[Dict[str, Any]] = []
|
|
|
|
if previous_conv:
|
|
try:
|
|
history = json.loads(previous_conv)
|
|
if isinstance(history, list):
|
|
messages.extend(history)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: Could not parse previous_conv JSON: {previous_conv}")
|
|
|
|
messages.append({"role": "user", "content": prompt})
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
"seed": seed,
|
|
}
|
|
|
|
if debug == "enable":
|
|
print(f"""[LM Studio Generate]
|
|
request query params:
|
|
|
|
- prompt: {prompt}
|
|
- url: {url}
|
|
- model: {model}
|
|
- temperature: {temperature}
|
|
- seed: {seed}
|
|
- messages: {messages}
|
|
""")
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(f"{url}/v1/chat/completions", json=payload, timeout=600.0)
|
|
response.raise_for_status()
|
|
|
|
lmstudio_response = response.json()
|
|
|
|
if debug == "enable":
|
|
print("[LM Studio Generate]\nResponse:\n")
|
|
pprint(lmstudio_response)
|
|
|
|
lmstudio_response_text = lmstudio_response['choices'][0]['message']['content']
|
|
if filter_thinking:
|
|
lmstudio_response_text = re.sub(r"<(?:think|thinking)>.*?</(?:think|thinking)>\s*", "", lmstudio_response_text, flags=re.DOTALL | re.IGNORECASE).strip()
|
|
|
|
messages.append({"role": "assistant", "content": lmstudio_response_text}) # Add assistant response to history
|
|
|
|
return (lmstudio_response_text, json.dumps(messages), format_conversation_history(messages),) # Return raw JSON and formatted history
|
|
except httpx.RequestError as e:
|
|
raise Exception(f"LM Studio Generate API request failed: {e}")
|
|
except json.JSONDecodeError:
|
|
raise Exception("LM Studio Generate API returned invalid JSON.")
|
|
except KeyError as e:
|
|
raise Exception(f"LM Studio Generate API response missing expected key: {e}. Full response: {lmstudio_response}")
|
|
|
|
|
|
class LMStudioGenerateAdvance:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
seed = random.randint(1, 2 ** 31)
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {
|
|
"multiline": True,
|
|
"default": "What is Art?"
|
|
}),
|
|
"debug": ("BOOLEAN", {"default": False}),
|
|
"url": ("STRING", {
|
|
"multiline": False,
|
|
"default": "http://localhost:1234" # Default LM Studio URL
|
|
}),
|
|
"model": ((), {}),
|
|
"system": ("STRING", {
|
|
"multiline": True,
|
|
"default": "You are an art expert, gracefully describing your knowledge in art domain.",
|
|
"title": "system"
|
|
}),
|
|
"seed": ("INT", {"default": seed, "min": 0, "max": 2 ** 31, "step": 1}),
|
|
"temperature": ("FLOAT", {"default": 0.8, "min": 0, "max": 1, "step": 0.05}),
|
|
"top_p": ("FLOAT", {"default": 0.9, "min": 0, "max": 1, "step": 0.05}),
|
|
"top_k": ("INT", {"default": 40, "min": 0, "max": 100, "step": 1}),
|
|
"max_tokens": ("INT", {"default": -1, "min": -1, "max": 4096, "step": 1}),
|
|
"filter_thinking": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("response", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_generate_advance"
|
|
CATEGORY = "LM Studio"
|
|
|
|
def lmstudio_generate_advance(self, prompt, debug, url, model, system, seed, temperature, top_p, top_k, max_tokens,
|
|
filter_thinking, previous_conv=None):
|
|
|
|
messages: List[Dict[str, Any]] = []
|
|
|
|
if previous_conv:
|
|
try:
|
|
history = json.loads(previous_conv)
|
|
if isinstance(history, list):
|
|
messages.extend(history)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: Could not parse previous_conv JSON: {previous_conv}")
|
|
|
|
if system:
|
|
if not messages or messages[0].get("role") != "system" or messages[0].get("content") != system:
|
|
messages.insert(0, {"role": "system", "content": system})
|
|
|
|
messages.append({"role": "user", "content": prompt})
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
"top_p": top_p,
|
|
"top_k": top_k,
|
|
"seed": seed,
|
|
}
|
|
if max_tokens != -1:
|
|
payload["max_tokens"] = max_tokens
|
|
|
|
|
|
if debug:
|
|
print(f"""[LM Studio Generate Advance]
|
|
request query params:
|
|
|
|
- prompt: {prompt}
|
|
- url: {url}
|
|
- model: {model}
|
|
- system: {system}
|
|
- messages: {messages}
|
|
- temperature: {temperature}
|
|
- top_p: {top_p}
|
|
- top_k: {top_k}
|
|
- max_tokens: {max_tokens}
|
|
- seed: {seed}
|
|
""")
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(f"{url}/v1/chat/completions", json=payload, timeout=600.0)
|
|
response.raise_for_status()
|
|
|
|
lmstudio_response = response.json()
|
|
|
|
if debug:
|
|
print("[LM Studio Generate Advance]\nResponse:\n")
|
|
pprint(lmstudio_response)
|
|
|
|
response_content = lmstudio_response['choices'][0]['message']['content']
|
|
if filter_thinking:
|
|
response_content = re.sub(r"<(?:think|thinking)>.*?</(?:think|thinking)>\s*", "", response_content, flags=re.DOTALL | re.IGNORECASE).strip()
|
|
|
|
messages.append({"role": "assistant", "content": response_content})
|
|
|
|
return (response_content, json.dumps(messages), format_conversation_history(messages),) # Return raw JSON and formatted history
|
|
except httpx.RequestError as e:
|
|
raise Exception(f"LM Studio Generate Advance API request failed: {e}")
|
|
except json.JSONDecodeError:
|
|
raise Exception("LM Studio Generate Advance API returned invalid JSON.")
|
|
except KeyError as e:
|
|
raise Exception(f"LM Studio Generate Advance API response missing expected key: {e}. Full response: {lmstudio_response}")
|
|
|
|
|
|
class LMStudioOptionsV2:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
seed = random.randint(1, 2 ** 31)
|
|
return {
|
|
"required": {
|
|
"enable_temperature": ("BOOLEAN", {"default": False}),
|
|
"temperature": ("FLOAT", {"default": 0.8, "min": 0, "max": 1, "step": 0.05}),
|
|
|
|
"enable_top_p": ("BOOLEAN", {"default": False}),
|
|
"top_p": ("FLOAT", {"default": 0.9, "min": 0, "max": 1, "step": 0.05}),
|
|
|
|
"enable_seed": ("BOOLEAN", {"default": False}),
|
|
"seed": ("INT", {"default": seed, "min": 0, "max": 2 ** 31, "step": 1}),
|
|
|
|
"enable_max_tokens": ("BOOLEAN", {"default": False}),
|
|
"max_tokens": ("INT", {"default": -1, "min": -1, "max": 4096, "step": 1}),
|
|
|
|
"debug": ("BOOLEAN", {"default": False}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LMSTUDIO_OPTIONS", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("options", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_options"
|
|
CATEGORY = "LM Studio"
|
|
|
|
def lmstudio_options(self, previous_conv=None, **kargs):
|
|
# Pass through previous_conv, or an empty JSON string if not provided
|
|
raw_conv = previous_conv if previous_conv is not None else json.dumps([])
|
|
formatted_conv = format_conversation_history(json.loads(raw_conv))
|
|
|
|
if kargs['debug']:
|
|
print("--- LM Studio options v2 dump\n")
|
|
pprint(kargs)
|
|
print("---------------------------------------------------------")
|
|
|
|
return (kargs, raw_conv, formatted_conv,)
|
|
|
|
|
|
class LMStudioConnectivityV2:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"url": ("STRING", {
|
|
"multiline": False,
|
|
"default": "http://localhost:1234"
|
|
}),
|
|
"model": ((), {}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LMSTUDIO_CONNECTIVITY", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("connection", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_connectivity"
|
|
CATEGORY = "LM Studio"
|
|
|
|
def lmstudio_connectivity(self, url, model, previous_conv=None):
|
|
data = {
|
|
"url": url,
|
|
"model": model,
|
|
}
|
|
# Pass through previous_conv, or an empty JSON string if not provided
|
|
raw_conv = previous_conv if previous_conv is not None else json.dumps([])
|
|
formatted_conv = format_conversation_history(json.loads(raw_conv))
|
|
|
|
return (data, raw_conv, formatted_conv,)
|
|
|
|
|
|
class LMStudioGenerateV2:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"system": ("STRING", {
|
|
"multiline": True,
|
|
"default": "You are an AI artist."
|
|
}),
|
|
"prompt": ("STRING", {
|
|
"multiline": True,
|
|
"default": "What is art?"
|
|
}),
|
|
"filter_thinking": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"connectivity": ("LMSTUDIO_CONNECTIVITY", {"forceInput": False},),
|
|
"options": ("LMSTUDIO_OPTIONS", {"forceInput": False},),
|
|
"images": ("IMAGE", {"forceInput": False},),
|
|
"previous_conv": ("STRING", {"forceInput": False, "multiline": True}),
|
|
"meta": ("LMSTUDIO_META", {"forceInput": False},),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "LMSTUDIO_META", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("result", "previous_conv", "meta", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_generate_v2"
|
|
CATEGORY = "LM Studio"
|
|
|
|
def get_request_options(self, options_dict: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""Extracts enabled options from the options dictionary."""
|
|
request_options = {}
|
|
if options_dict is None:
|
|
return request_options
|
|
|
|
option_map = {
|
|
"enable_temperature": "temperature",
|
|
"enable_top_p": "top_p",
|
|
"enable_seed": "seed",
|
|
"enable_max_tokens": "max_tokens",
|
|
}
|
|
|
|
for enable_key, actual_key in option_map.items():
|
|
if options_dict.get(enable_key, False):
|
|
value = options_dict.get(actual_key)
|
|
if actual_key == "max_tokens" and value == -1:
|
|
request_options[actual_key] = None
|
|
else:
|
|
request_options[actual_key] = value
|
|
return request_options
|
|
|
|
def lmstudio_generate_v2(self, system, prompt, filter_thinking, previous_conv=None, options=None, connectivity=None, images=None, meta=None):
|
|
|
|
current_connectivity = connectivity
|
|
current_options = options
|
|
|
|
if meta is not None:
|
|
if connectivity is None and "connectivity" in meta:
|
|
current_connectivity = meta["connectivity"]
|
|
if options is None and "options" in meta:
|
|
current_options = meta["options"]
|
|
else:
|
|
meta = {}
|
|
|
|
if current_connectivity is None:
|
|
raise Exception("Required input 'connectivity' or 'connectivity' in 'meta'.")
|
|
|
|
url = current_connectivity['url']
|
|
model = current_connectivity['model']
|
|
|
|
debug_print = True if current_options is not None and current_options.get('debug', False) else False
|
|
|
|
messages: List[Dict[str, Any]] = []
|
|
|
|
if previous_conv:
|
|
try:
|
|
history = json.loads(previous_conv)
|
|
if isinstance(history, list):
|
|
messages.extend(history)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: Could not parse previous_conv JSON: {previous_conv}")
|
|
|
|
if system:
|
|
if not messages or messages[0].get("role") != "system" or messages[0].get("content") != system:
|
|
messages.insert(0, {"role": "system", "content": system})
|
|
|
|
if images is not None:
|
|
image_content_parts = []
|
|
for (batch_number, image) in enumerate(images):
|
|
i = 255. * image.cpu().numpy()
|
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
|
buffered = BytesIO()
|
|
img.save(buffered, format="PNG")
|
|
img_bytes = base64.b64encode(buffered.getvalue())
|
|
image_content_parts.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{str(img_bytes, 'utf-8')}"}})
|
|
|
|
user_content = [{"type": "text", "text": prompt}] + image_content_parts
|
|
messages.append({"role": "user", "content": user_content})
|
|
else:
|
|
messages.append({"role": "user", "content": prompt})
|
|
|
|
request_options = self.get_request_options(current_options)
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
**request_options
|
|
}
|
|
|
|
if debug_print:
|
|
print(f"""
|
|
--- LM Studio generate v2 request:
|
|
|
|
url: {url}
|
|
model: {model}
|
|
system: {system}
|
|
prompt: {prompt}
|
|
images: {0 if images is None else len(images)}
|
|
messages: {messages}
|
|
options: {request_options}
|
|
---------------------------------------------------------
|
|
""")
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(f"{url}/v1/chat/completions", json=payload, timeout=600.0)
|
|
response.raise_for_status()
|
|
|
|
lmstudio_response = response.json()
|
|
|
|
if debug_print:
|
|
print("\n--- LM Studio generate v2 response:")
|
|
pprint(lmstudio_response)
|
|
print("---------------------------------------------------------")
|
|
|
|
response_content = lmstudio_response['choices'][0]['message']['content']
|
|
if filter_thinking:
|
|
response_content = re.sub(r"<(?:think|thinking)>.*?</(?:think|thinking)>\s*", "", response_content, flags=re.DOTALL | re.IGNORECASE).strip()
|
|
|
|
messages.append({"role": "assistant", "content": response_content})
|
|
|
|
meta["connectivity"] = current_connectivity
|
|
meta["options"] = current_options
|
|
|
|
return (response_content, json.dumps(messages), meta, format_conversation_history(messages),) # Return raw JSON, meta, and formatted history
|
|
except httpx.RequestError as e:
|
|
raise Exception(f"LM Studio Generate V2 API request failed: {e}")
|
|
except json.JSONDecodeError:
|
|
raise Exception("LM Studio Generate V2 API returned invalid JSON.")
|
|
except KeyError as e:
|
|
raise Exception(f"LM Studio Generate V2 API response missing expected key: {e}. Full response: {lmstudio_response}")
|
|
|
|
|
|
class LMStudioSequentialPrompt:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
seed = random.randint(1, 2 ** 31)
|
|
return {
|
|
"required": {
|
|
"url": ("STRING", {
|
|
"multiline": False,
|
|
"default": "http://localhost:1234"
|
|
}),
|
|
"model": ((), {}),
|
|
"prompt_templates": ("STRING", {
|
|
"multiline": True,
|
|
"default": "Step 1: Summarize the following text: 'The quick brown fox jumps over the lazy dog.'\n---\nStep 2: Explain the meaning of 'quick brown fox'.",
|
|
"title": "Prompt Templates (separated by delimiter)"
|
|
}),
|
|
"delimiter": ("STRING", {
|
|
"multiline": False,
|
|
"default": "---",
|
|
"title": "Delimiter for Prompt Templates"
|
|
}),
|
|
"temperature": ("FLOAT", {"default": 0.8, "min": 0, "max": 1, "step": 0.05}),
|
|
"seed": ("INT", {"default": seed, "min": 0, "max": 2 ** 31, "step": 1}),
|
|
"send_as_chat": ("BOOLEAN", {"default": False, "label_on": "Send Current Prompt Only", "label_off": "Send Full History"}),
|
|
"debug": ("BOOLEAN", {"default": False}),
|
|
"filter_thinking": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("final_response", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_sequential_prompt"
|
|
CATEGORY = "LM Studio/Sequential"
|
|
|
|
def lmstudio_sequential_prompt(self, url, model, prompt_templates, delimiter, temperature, seed, send_as_chat, debug, filter_thinking, previous_conv=None):
|
|
messages: List[Dict[str, Any]] = []
|
|
final_response_content = ""
|
|
|
|
if previous_conv:
|
|
try:
|
|
history = json.loads(previous_conv)
|
|
if isinstance(history, list):
|
|
messages.extend(history)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: Could not parse previous_conv JSON: {previous_conv}")
|
|
|
|
templates = [t.strip() for t in prompt_templates.split(delimiter) if t.strip()]
|
|
|
|
for i, template in enumerate(templates):
|
|
user_prompt = template
|
|
|
|
messages.append({"role": "user", "content": user_prompt})
|
|
|
|
messages_to_send = []
|
|
if send_as_chat:
|
|
messages_to_send = [{"role": "user", "content": user_prompt}]
|
|
else:
|
|
messages_to_send = list(messages)
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages_to_send,
|
|
"temperature": temperature,
|
|
"seed": seed,
|
|
}
|
|
|
|
if debug:
|
|
print(f"""[LM Studio Sequential Prompt - Step {i+1}]
|
|
request query params:
|
|
|
|
- url: {url}
|
|
- model: {model}
|
|
- current_user_prompt: {user_prompt}
|
|
- messages_sent_to_api: {messages_to_send}
|
|
- temperature: {temperature}
|
|
- seed: {seed}
|
|
- send_as_chat (only current prompt): {send_as_chat}
|
|
""")
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(f"{url}/v1/chat/completions", json=payload, timeout=600.0)
|
|
response.raise_for_status()
|
|
|
|
lmstudio_response = response.json()
|
|
|
|
if debug:
|
|
print(f"[LM Studio Sequential Prompt - Step {i+1}]\nResponse:\n")
|
|
pprint(lmstudio_response)
|
|
|
|
assistant_response_content = lmstudio_response['choices'][0]['message']['content']
|
|
if filter_thinking:
|
|
assistant_response_content = re.sub(r"<(?:think|thinking)>.*?</(?:think|thinking)>\s*", "", assistant_response_content, flags=re.DOTALL | re.IGNORECASE).strip()
|
|
|
|
messages.append({"role": "assistant", "content": assistant_response_content})
|
|
final_response_content = assistant_response_content
|
|
|
|
except httpx.RequestError as e:
|
|
raise Exception(f"LM Studio Sequential Prompt API request failed at step {i+1}: {e}")
|
|
except json.JSONDecodeError:
|
|
raise Exception(f"LM Studio Sequential Prompt API returned invalid JSON at step {i+1}.")
|
|
except KeyError as e:
|
|
raise Exception(f"LM Studio Sequential Prompt API response missing expected key: {e}. Full response: {lmstudio_response}")
|
|
|
|
return (final_response_content, json.dumps(messages), format_conversation_history(messages),)
|
|
|
|
|
|
class LMStudioSequentialPromptAdvanced:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
seed = random.randint(1, 2 ** 31)
|
|
return {
|
|
"required": {
|
|
"url": ("STRING", {
|
|
"multiline": False,
|
|
"default": "http://localhost:1234" # Default LM Studio URL
|
|
}),
|
|
"model": ((), {}),
|
|
"system": ("STRING", {
|
|
"multiline": True,
|
|
"default": "You are a helpful assistant.",
|
|
"title": "system"
|
|
}),
|
|
"prompt_templates": ("STRING", {
|
|
"multiline": True,
|
|
"default": "Step 1: Summarize the following text: 'The quick brown fox jumps over the lazy dog.'\n---\nStep 2: Explain the meaning of 'quick brown fox'.",
|
|
"title": "Prompt Templates (separated by delimiter)"
|
|
}),
|
|
"delimiter": ("STRING", {
|
|
"multiline": False,
|
|
"default": "---",
|
|
"title": "Delimiter for Prompt Templates"
|
|
}),
|
|
"debug": ("BOOLEAN", {"default": False}),
|
|
"seed": ("INT", {"default": seed, "min": 0, "max": 2 ** 31, "step": 1}),
|
|
"temperature": ("FLOAT", {"default": 0.8, "min": 0, "max": 1, "step": 0.05}),
|
|
"top_p": ("FLOAT", {"default": 0.9, "min": 0, "max": 1, "step": 0.05}),
|
|
"top_k": ("INT", {"default": 40, "min": 0, "max": 100, "step": 1}),
|
|
"max_tokens": ("INT", {"default": -1, "min": -1, "max": 4096, "step": 1}),
|
|
"send_as_chat": ("BOOLEAN", {"default": False, "label_on": "Send Current Prompt Only", "label_off": "Send Full History"}),
|
|
"filter_thinking": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"previous_conv": ("STRING", {"forceInput": True, "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING",) # Added read_conv output
|
|
RETURN_NAMES = ("final_response", "previous_conv", "read_conv",) # Added read_conv output name
|
|
FUNCTION = "lmstudio_sequential_prompt_advanced"
|
|
CATEGORY = "LM Studio/Sequential"
|
|
|
|
def lmstudio_sequential_prompt_advanced(self, url, model, system, prompt_templates, delimiter, debug, seed, temperature, top_p, top_k, max_tokens, send_as_chat, filter_thinking, previous_conv=None):
|
|
messages: List[Dict[str, Any]] = []
|
|
final_response_content = ""
|
|
|
|
if previous_conv:
|
|
try:
|
|
history = json.loads(previous_conv)
|
|
if isinstance(history, list):
|
|
messages.extend(history)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: Could not parse previous_conv JSON: {previous_conv}")
|
|
|
|
if system:
|
|
if not messages or messages[0].get("role") != "system" or messages[0].get("content") != system:
|
|
messages.insert(0, {"role": "system", "content": system})
|
|
|
|
templates = [t.strip() for t in prompt_templates.split(delimiter) if t.strip()]
|
|
|
|
for i, template in enumerate(templates):
|
|
user_prompt = template
|
|
|
|
messages.append({"role": "user", "content": user_prompt})
|
|
|
|
messages_to_send = []
|
|
if send_as_chat:
|
|
if i == 0 and system:
|
|
messages_to_send = [{"role": "system", "content": system}, {"role": "user", "content": user_prompt}]
|
|
else:
|
|
messages_to_send = [{"role": "user", "content": user_prompt}]
|
|
else:
|
|
messages_to_send = list(messages)
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages_to_send,
|
|
"temperature": temperature,
|
|
"top_p": top_p,
|
|
"top_k": top_k,
|
|
"seed": seed,
|
|
}
|
|
if max_tokens != -1:
|
|
payload["max_tokens"] = max_tokens
|
|
|
|
if debug:
|
|
print(f"""[LM Studio Sequential Prompt Advanced - Step {i+1}]
|
|
request query params:
|
|
|
|
- url: {url}
|
|
- model: {model}
|
|
- current_user_prompt: {user_prompt}
|
|
- messages_sent_to_api: {messages_to_send}
|
|
- temperature: {temperature}
|
|
- top_p: {top_p}
|
|
- top_k: {top_k}
|
|
- max_tokens: {max_tokens}
|
|
- seed: {seed}
|
|
- send_as_chat (only current prompt): {send_as_chat}
|
|
""")
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(f"{url}/v1/chat/completions", json=payload, timeout=600.0)
|
|
response.raise_for_status()
|
|
|
|
lmstudio_response = response.json()
|
|
|
|
if debug:
|
|
print(f"[LM Studio Sequential Prompt Advanced - Step {i+1}]\nResponse:\n")
|
|
pprint(lmstudio_response)
|
|
|
|
assistant_response_content = lmstudio_response['choices'][0]['message']['content']
|
|
if filter_thinking:
|
|
assistant_response_content = re.sub(r"<(?:think|thinking)>.*?</(?:think|thinking)>\s*", "", assistant_response_content, flags=re.DOTALL | re.IGNORECASE).strip()
|
|
|
|
messages.append({"role": "assistant", "content": assistant_response_content})
|
|
final_response_content = assistant_response_content
|
|
|
|
except httpx.RequestError as e:
|
|
raise Exception(f"LM Studio Sequential Prompt Advanced API request failed at step {i+1}: {e}")
|
|
except json.JSONDecodeError:
|
|
raise Exception(f"LM Studio Sequential Prompt Advanced API returned invalid JSON at step {i+1}.")
|
|
except KeyError as e:
|
|
raise Exception(f"LM Studio Sequential Prompt Advanced API response missing expected key: {e}. Full response: {lmstudio_response}")
|
|
|
|
return (final_response_content, json.dumps(messages), format_conversation_history(messages),)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"LMStudioVision": LMStudioVision,
|
|
"LMStudioGenerate": LMStudioGenerate,
|
|
"LMStudioGenerateAdvance": LMStudioGenerateAdvance,
|
|
"LMStudioOptionsV2": LMStudioOptionsV2,
|
|
"LMStudioConnectivityV2": LMStudioConnectivityV2,
|
|
"LMStudioGenerateV2": LMStudioGenerateV2,
|
|
"LMStudioSequentialPrompt": LMStudioSequentialPrompt,
|
|
"LMStudioSequentialPromptAdvanced": LMStudioSequentialPromptAdvanced,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"LMStudioVision": "LM Studio Vision",
|
|
"LMStudioGenerate": "LM Studio Generate",
|
|
"LMStudioGenerateAdvance": "LM Studio Generate Advance",
|
|
"LMStudioOptionsV2": "LM Studio Options V2",
|
|
"LMStudioConnectivityV2": "LM Studio Connectivity V2",
|
|
"LMStudioGenerateV2": "LM Studio Generate V2",
|
|
"LMStudioSequentialPrompt": "LM Studio Sequential Prompt",
|
|
"LMStudioSequentialPromptAdvanced": "LM Studio Sequential Prompt Advanced",
|
|
}
|