V2.0.0 - Mistral
This commit is contained in:
+9
-1
@@ -7,6 +7,7 @@ from ..components import utility
|
||||
from ..components.API import api_helper
|
||||
import folder_paths
|
||||
|
||||
import re
|
||||
import random
|
||||
import argparse
|
||||
import json
|
||||
@@ -41,7 +42,7 @@ class PrimereApiProcessor:
|
||||
SECTION_TITLES = [
|
||||
{"before": "processor", "name": "primere_api_proc", "title": "🧭 API Setup", "color": "#1B263B", "text_color": "#EAF1F8", "label": "Setup API processor, select related provider and service."},
|
||||
{"before": "auto_save_result", "name": "primere_save_api_result", "title": "💾 API result save", "color": "#1B263B", "text_color": "#EAF1F8", "label": "Save API results. Define main path, add subdirectory structure, add filename prefixes. Save related data to .txt or .json file."},
|
||||
{"after": "save_data_to_txt", "name": "primere_api_body", "title": "⚙ API body parameters", "color": "#1B263B", "text_color": "#EAF1F8", "label": "Set API body custom parameters."},
|
||||
{"after": "save_response_to_json", "name": "primere_api_body", "title": "⚙ API body parameters", "color": "#1B263B", "text_color": "#EAF1F8", "label": "Set API body custom parameters."},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -69,6 +70,7 @@ class PrimereApiProcessor:
|
||||
"image_quality": ("INT", {"default": 95, "min": 1, "max": 100, "step": 1}),
|
||||
"save_data_to_json": ("BOOLEAN", {"default": False}),
|
||||
"save_data_to_txt": ("BOOLEAN", {"default": False}),
|
||||
"save_response_to_json": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
|
||||
cls.optional_inputs = {
|
||||
@@ -378,6 +380,7 @@ class PrimereApiProcessor:
|
||||
image_quality = kwargs.get('image_quality', 95)
|
||||
save_data_to_json = kwargs.get('save_data_to_json', False)
|
||||
save_data_to_txt = kwargs.get('save_data_to_txt', False)
|
||||
save_response_to_json = kwargs.get('save_response_to_json', False)
|
||||
add_model_to_path = kwargs.get('add_model_to_path', False)
|
||||
|
||||
model_subdir = None
|
||||
@@ -422,6 +425,11 @@ class PrimereApiProcessor:
|
||||
}
|
||||
file_output.save_metadata(save_data, json_file, txt_file, save_data_to_json, save_data_to_txt, used_values_output)
|
||||
|
||||
if save_response_to_json:
|
||||
response_json_file = os.path.splitext(saved_path)[0] + '.response.json'
|
||||
with open(response_json_file, 'w', encoding='utf-8') as response_file:
|
||||
json.dump(api_result_debug, response_file, ensure_ascii=False, indent=4, default=str)
|
||||
|
||||
PromptServer.instance.send_sync("primere.save_result", {
|
||||
"node_id": unique_id,
|
||||
"status": "success",
|
||||
|
||||
@@ -26,6 +26,10 @@ def get_api_config(name: str) -> dict:
|
||||
config_json['FAL']['APIKEY'] = os.environ.get("FAL_API_KEY") or config_json['FAL']['APIKEY']
|
||||
case "Elevenlabs":
|
||||
config_json['Elevenlabs']['APIKEY'] = os.environ.get("ELEVENLABS_API_KEY") or config_json['Elevenlabs']['APIKEY']
|
||||
case "Mistral":
|
||||
config_json['Mistral']['APIKEY'] = os.environ.get("MISTRAL_API_KEY") or config_json['Mistral']['APIKEY']
|
||||
case "Opencode":
|
||||
config_json['Opencode']['APIKEY'] = os.environ.get("OPENCODE_API_KEY") or config_json['Opencode']['APIKEY']
|
||||
|
||||
return config_json
|
||||
|
||||
@@ -54,6 +58,9 @@ def create_api_client(api_provider, config_json):
|
||||
from elevenlabs.client import ElevenLabs
|
||||
load_dotenv()
|
||||
APIClient = ElevenLabs(api_key=config_json['Elevenlabs']['APIKEY'])
|
||||
case "Mistral":
|
||||
from mistralai.client import Mistral
|
||||
APIClient = Mistral(api_key=config_json['Mistral']['APIKEY'])
|
||||
case _:
|
||||
return (None, None)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import numpy as np
|
||||
import torch
|
||||
import comfy.utils
|
||||
import types
|
||||
import ast
|
||||
|
||||
from . import request_exceptions
|
||||
|
||||
@@ -623,6 +624,20 @@ def apply_response_handler(schema: dict[str, Any] | None, api_result: Any, provi
|
||||
return handler(api_result, **kwargs)
|
||||
|
||||
def sanitize_debug_value(value: Any) -> Any:
|
||||
if isinstance(value, str):
|
||||
text_value = value.strip()
|
||||
if text_value:
|
||||
try:
|
||||
parsed_json = json.loads(text_value)
|
||||
return sanitize_debug_value(parsed_json)
|
||||
except Exception:
|
||||
normalized = re.sub(r"\bUnset\s*\(\s*\)", "None", text_value)
|
||||
try:
|
||||
parsed_literal = ast.literal_eval(normalized)
|
||||
return sanitize_debug_value(parsed_literal)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if isinstance(value, torch.Tensor):
|
||||
return f"[torch.Tensor omitted: shape={tuple(value.shape)}, dtype={value.dtype}]"
|
||||
|
||||
@@ -689,6 +704,7 @@ def sanitize_debug_value(value: Any) -> Any:
|
||||
|
||||
def sanitize_api_debug_payload(value: Any) -> Any:
|
||||
return redact_reference_images(sanitize_debug_value(value))
|
||||
|
||||
def apply_parameter_constraints(selected_parameters: dict[str, Any], schema: dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not isinstance(schema, dict):
|
||||
return selected_parameters
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import dataclasses
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _normalize_input(value: Any) -> Any:
|
||||
if isinstance(value, str):
|
||||
text_value = value.strip()
|
||||
if not text_value:
|
||||
return value
|
||||
try:
|
||||
return json.loads(text_value)
|
||||
except Exception:
|
||||
normalized = re.sub(r"\bUnset\s*\(\s*\)", "None", text_value)
|
||||
try:
|
||||
return ast.literal_eval(normalized)
|
||||
except Exception:
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def _to_builtin(value: Any) -> Any:
|
||||
value = _normalize_input(value)
|
||||
|
||||
if isinstance(value, dict):
|
||||
return {str(k): _to_builtin(v) for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [_to_builtin(item) for item in value]
|
||||
if isinstance(value, tuple):
|
||||
return [_to_builtin(item) for item in value]
|
||||
if dataclasses.is_dataclass(value):
|
||||
return _to_builtin(dataclasses.asdict(value))
|
||||
if hasattr(value, "__dict__") and not isinstance(value, (str, int, float, bool, type(None))):
|
||||
return {str(k): _to_builtin(v) for k, v in vars(value).items() if not str(k).startswith("_")}
|
||||
return value
|
||||
|
||||
|
||||
def _extract_content(payload: Any) -> str | None:
|
||||
data = _to_builtin(payload)
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
|
||||
choices = data.get("choices")
|
||||
if not isinstance(choices, list) or len(choices) == 0:
|
||||
return None
|
||||
|
||||
first_choice = choices[0] if isinstance(choices[0], dict) else None
|
||||
if not isinstance(first_choice, dict):
|
||||
return None
|
||||
|
||||
message = first_choice.get("message")
|
||||
if not isinstance(message, dict):
|
||||
return None
|
||||
|
||||
content = message.get("content")
|
||||
if isinstance(content, str) and content.strip():
|
||||
return content
|
||||
return None
|
||||
|
||||
|
||||
def handle_response(api_result, schema=None, loaded_client=None, response_url=None, client=None, sdk_context=None):
|
||||
payload = _to_builtin(api_result)
|
||||
content = _extract_content(payload)
|
||||
|
||||
if content is not None:
|
||||
return ["text_result", content.encode("utf-8")]
|
||||
|
||||
fallback = {
|
||||
"error": "Mistral content not found in response. Saved full response instead.",
|
||||
"response": payload,
|
||||
}
|
||||
fallback_text = json.dumps(fallback, ensure_ascii=False, indent=2, default=str)
|
||||
return ["text_result", fallback_text.encode("utf-8")]
|
||||
@@ -1,5 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from . import response_helper
|
||||
|
||||
|
||||
@@ -122,8 +122,8 @@ def resolve_output_file(output_path_input, base_dir, subdirs, prefix, delimiter,
|
||||
)
|
||||
|
||||
output_file = os.path.abspath(os.path.join(resolved, filename))
|
||||
json_file = os.path.splitext(output_file)[0] + '.json'
|
||||
txt_file = os.path.splitext(output_file)[0] + '.txt'
|
||||
json_file = os.path.splitext(output_file)[0] + '_meta.json'
|
||||
txt_file = os.path.splitext(output_file)[0] + '_meta.txt'
|
||||
|
||||
return output_file, json_file, txt_file
|
||||
|
||||
|
||||
@@ -18,9 +18,34 @@
|
||||
"fake_reference_image_value_1",
|
||||
"fake_reference_image_value_2"
|
||||
],
|
||||
"aspect_ratio": ["auto", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"],
|
||||
"duration": ["auto", 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
"resolution": ["480p", "720p"],
|
||||
"aspect_ratio": [
|
||||
"auto",
|
||||
"21:9",
|
||||
"16:9",
|
||||
"4:3",
|
||||
"1:1",
|
||||
"3:4",
|
||||
"9:16"
|
||||
],
|
||||
"duration": [
|
||||
"auto",
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15
|
||||
],
|
||||
"resolution": [
|
||||
"480p",
|
||||
"720p"
|
||||
],
|
||||
"video_urls": [
|
||||
"fake_video_urls_value_1",
|
||||
"fake_video_urls_value_2"
|
||||
@@ -53,5 +78,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Mistral": {
|
||||
"Chat": {
|
||||
"provider": "Mistral",
|
||||
"service": "Chat",
|
||||
"response_handler": "Mistral_Chat.py",
|
||||
"reference_images_handler": "Mistral.py",
|
||||
"import_modules": [
|
||||
"import your_provider_sdk",
|
||||
"from your_provider_sdk import types"
|
||||
],
|
||||
"possible_parameters": {
|
||||
"model": [
|
||||
"example-model-1",
|
||||
"example-model-2"
|
||||
],
|
||||
"response": [
|
||||
"fake_response_value_1",
|
||||
"fake_response_value_2"
|
||||
],
|
||||
"role": [
|
||||
"fake_role_value_1",
|
||||
"fake_role_value_2"
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "SDK",
|
||||
"endpoint": "mistral.chat.complete",
|
||||
"sdk_call": {
|
||||
"args": [],
|
||||
"kwargs": {
|
||||
"model": "{{model}}",
|
||||
"messages": [
|
||||
{
|
||||
"role": "{{role}}",
|
||||
"content": "{{prompt}}"
|
||||
}
|
||||
],
|
||||
"stream": false,
|
||||
"response_format": {
|
||||
"type": "{{response}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,13 +1,13 @@
|
||||
result = fal_client.subscribe("bytedance/seedance-2.0/fast/reference-to-video",
|
||||
arguments={
|
||||
"prompt": prompt,
|
||||
"image_urls": reference_image,
|
||||
"video_urls": video_urls,
|
||||
"audio_urls": audio_urls,
|
||||
"resolution": resolution,
|
||||
"seed": seed,
|
||||
"duration": "auto",
|
||||
"aspect_ratio": "auto",
|
||||
"with_logs": with_logs
|
||||
res = mistral.chat.complete(
|
||||
model=model,
|
||||
messages=[
|
||||
{
|
||||
"role": role,
|
||||
"content": prompt
|
||||
},
|
||||
],
|
||||
stream=False,
|
||||
response_format={
|
||||
"type": response,
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user