diff --git a/__init__.py b/__init__.py index 2dd7519..e3cdd81 100644 --- a/__init__.py +++ b/__init__.py @@ -53,6 +53,11 @@ from .modules.textInferenceMessages import RunwareTextInferenceMessages from .modules.textInferenceMessage import RunwareTextInferenceMessage from .modules.textInference import RunwareTextInference from .modules.textInferenceSettings import RunwareTextInferenceSettings +from .modules.textInferenceToolChoice import RunwareTextInferenceToolChoice +from .modules.textInferenceSettingsCache import RunwareTextInferenceSettingsCache +from .modules.textInferenceInputs import RunwareTextInferenceInputs +from .modules.textInferenceInputsImages import RunwareTextInferenceInputsImages +from .modules.textInferenceInputsVideos import RunwareTextInferenceInputsVideos from .modules.audioSections import RunwareAudioSections from .modules.audioInferenceInputs import audioInferenceInputs from .modules.audioSettings import RunwareAudioSettings @@ -177,6 +182,11 @@ NODE_CLASS_MAPPINGS = { "Runware Text Inference Message": RunwareTextInferenceMessage, "Runware Text Inference": RunwareTextInference, "Runware Text Inference Settings": RunwareTextInferenceSettings, + "Runware Text Inference Tool Choice": RunwareTextInferenceToolChoice, + "Runware Text Inference Settings Cache": RunwareTextInferenceSettingsCache, + "Runware Text Inference Inputs": RunwareTextInferenceInputs, + "Runware Text Inference Inputs Images": RunwareTextInferenceInputsImages, + "Runware Text Inference Inputs Videos": RunwareTextInferenceInputsVideos, "Runware Audio Sections": RunwareAudioSections, "Runware Audio Inference Inputs": audioInferenceInputs, "Runware Audio Inference Settings": RunwareAudioSettings, diff --git a/clientlibs/types.js b/clientlibs/types.js index 26ed9f0..12443e7 100644 --- a/clientlibs/types.js +++ b/clientlibs/types.js @@ -89,6 +89,11 @@ const RUNWARE_NODE_TYPES = { TEXTINFERENCEMESSAGES: "Runware Text Inference Messages", TEXTINFERENCEMESSAGE: "Runware Text Inference Message", TEXTINFERENCESETTINGS: "Runware Text Inference Settings", + TEXTINFERENCETOOLCHOICE: "Runware Text Inference Tool Choice", + TEXTINFERENCESETTINGSCACHE: "Runware Text Inference Settings Cache", + TEXTINFERENCEINPUTS: "Runware Text Inference Inputs", + TEXTINFERENCEINPUTSIMAGES: "Runware Text Inference Inputs Images", + TEXTINFERENCEINPUTSVIDEOS: "Runware Text Inference Inputs Videos", AUDIOSECTIONS: "Runware Audio Sections", AUDIOINFERENCEINPUTS: "Runware Audio Inference Inputs", AUDIOSETTINGS: "Runware Audio Inference Settings", @@ -328,6 +333,26 @@ const RUNWARE_NODE_PROPS = { bgColor: DEFAULT_BGCOLOR, colorModeOnly: true, }, + [RUNWARE_NODE_TYPES.TEXTINFERENCETOOLCHOICE]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGSCACHE]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.TEXTINFERENCEINPUTSIMAGES]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.TEXTINFERENCEINPUTSVIDEOS]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.TEXTINFERENCEINPUTS]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, [RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGS]: { bgColor: DEFAULT_BGCOLOR, colorModeOnly: true, diff --git a/clientlibs/utils.js b/clientlibs/utils.js index 46140bd..984a876 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -3275,6 +3275,11 @@ function textModelSearchFilterHandler(textModelSearchNode) { "openai:gpt@5.4-mini (GPT-5.4 Mini)", "openai:gpt@5.4-nano (GPT-5.4 Nano)", ], + "Anthropic": [ + "anthropic:claude@opus-4.7 (Claude Opus 4.7)", + "anthropic:claude@sonnet-4.6 (Claude Sonnet 4.6)", + "anthropic:claude@haiku-4.5 (Claude Haiku 4.5)", + ], "Google": [ "google:gemini@3-flash (Gemini 3 Flash)", ], diff --git a/modules/textInference.py b/modules/textInference.py index 2d514fe..3ab898a 100644 --- a/modules/textInference.py +++ b/modules/textInference.py @@ -25,6 +25,12 @@ class RunwareTextInference: "providerSettings": ("RUNWAREPROVIDERSETTINGS", { "tooltip": "Optional provider-specific settings (wrapped by provider prefix from the model AIR).", }), + "toolChoice": ("RUNWARETEXTINFERENCETOOLCHOICE", { + "tooltip": "Connect Runware Text Inference Tool Choice for tool-calling strategy (auto / any / tool / none).", + }), + "inputs": ("RUNWARETEXTINFERENCEINPUTS", { + "tooltip": "Connect Runware Text Inference Inputs for inputs.images / inputs.videos (URLs or mediaUUIDs).", + }), }, } @@ -32,7 +38,7 @@ class RunwareTextInference: RETURN_NAMES = ("text",) FUNCTION = "infer_text" CATEGORY = "Runware/Text" - DESCRIPTION = "Run text/chat inference using Runware textInference. Connect Text Model and Text Inference Messages." + DESCRIPTION = "Run text/chat inference using Runware textInference. Connect Text Model and Text Inference Messages. Optional: Settings, provider settings, tool choice, inputs (images/videos)." @staticmethod def _extract_text_from_data_item(item: Dict[str, Any]) -> Optional[str]: @@ -70,6 +76,8 @@ class RunwareTextInference: messages: List[Dict[str, str]] = kwargs.get("messages") or [] text_settings = kwargs.get("settings") provider_settings = kwargs.get("providerSettings") + tool_choice = kwargs.get("toolChoice") + text_inputs = kwargs.get("inputs") if not model: raise Exception("model is empty.") @@ -94,6 +102,12 @@ class RunwareTextInference: provider_name = model.split(":")[0] if ":" in model else model task["providerSettings"] = {provider_name: provider_settings} + if tool_choice is not None and isinstance(tool_choice, dict) and len(tool_choice) > 0: + task["toolChoice"] = tool_choice + + if text_inputs is not None and isinstance(text_inputs, dict) and len(text_inputs) > 0: + task["inputs"] = dict(text_inputs) + gen_config = [task] print(f"[Runware Text Inference] Request: {rwUtils.safe_json_dumps(gen_config, indent=2)}") diff --git a/modules/textInferenceInputs.py b/modules/textInferenceInputs.py new file mode 100644 index 0000000..1097488 --- /dev/null +++ b/modules/textInferenceInputs.py @@ -0,0 +1,48 @@ +"""Runware Text Inference Inputs — merge Images + Videos blocks into task.inputs for textInference.""" + +from typing import Any, Dict, Tuple + + +class RunwareTextInferenceInputs: + """Merge Runware Text Inference Inputs Images and/or Videos into a single inputs dict.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + "optional": { + "images": ("RUNWARETEXTINFERENCEINPUTSIMAGES", { + "tooltip": "Connect Runware Text Inference Inputs Images for inputs.images[].", + }), + "videos": ("RUNWARETEXTINFERENCEINPUTSVIDEOS", { + "tooltip": "Connect Runware Text Inference Inputs Videos for inputs.videos[].", + }), + }, + } + + RETURN_TYPES = ("RUNWARETEXTINFERENCEINPUTS",) + RETURN_NAMES = ("inputs",) + FUNCTION = "merge_inputs" + CATEGORY = "Runware/Text" + DESCRIPTION = ( + "Combine Runware Text Inference Inputs Images and/or Videos into inputs for Runware Text Inference " + "(inputs.images, inputs.videos)." + ) + + def merge_inputs(self, **kwargs) -> Tuple[Dict[str, Any], ...]: + images_block = kwargs.get("images") + videos_block = kwargs.get("videos") + + out: Dict[str, Any] = {} + + if isinstance(images_block, dict) and images_block.get("images"): + imgs = images_block["images"] + if isinstance(imgs, list) and len(imgs) > 0: + out["images"] = [str(x).strip() for x in imgs if x is not None and str(x).strip()] + + if isinstance(videos_block, dict) and videos_block.get("videos"): + vids = videos_block["videos"] + if isinstance(vids, list) and len(vids) > 0: + out["videos"] = [str(x).strip() for x in vids if x is not None and str(x).strip()] + + return (out,) diff --git a/modules/textInferenceInputsImages.py b/modules/textInferenceInputsImages.py new file mode 100644 index 0000000..6d44a83 --- /dev/null +++ b/modules/textInferenceInputsImages.py @@ -0,0 +1,50 @@ +"""Runware Text Inference Inputs Images — builds inputs.images from ComfyUI IMAGE tensors.""" + +from typing import Any, Dict, List, Tuple + +from .utils import runwareUtils as rwUtils + + +class RunwareTextInferenceInputsImages: + """Collect IMAGE tensors and convert them for textInference task.inputs.images. + + Each connected IMAGE is converted via runwareUtils.convertTensor2IMG, which returns + either a cached mediaUUID (when caching is enabled) or a base64 data URI. + """ + + _MAX_IMAGES = 8 + + @classmethod + def INPUT_TYPES(cls): + optional: Dict[str, tuple] = {} + for i in range(1, cls._MAX_IMAGES + 1): + ordinal = rwUtils.getOrdinal(i) + optional[f"Image {i}"] = ("IMAGE", { + "tooltip": ( + f"{ordinal.capitalize()} image for the model. Connect a Load Image (or any IMAGE source) node. " + "Tensor is converted to base64 / mediaUUID before being sent to the API." + ), + }) + return {"required": {}, "optional": optional} + + RETURN_TYPES = ("RUNWARETEXTINFERENCEINPUTSIMAGES",) + RETURN_NAMES = ("images",) + FUNCTION = "createImages" + CATEGORY = "Runware/Text" + DESCRIPTION = ( + "Collect one or more IMAGE tensors (Load Image, generated images, etc.) for Runware Text Inference " + "inputs.images. Connect the output to Runware Text Inference Inputs." + ) + + def createImages(self, **kwargs) -> Tuple[Dict[str, Any], ...]: + urls: List[str] = [] + for i in range(1, self._MAX_IMAGES + 1): + tensor = kwargs.get(f"Image {i}", None) + if tensor is None: + continue + urls.append(rwUtils.convertTensor2IMG(tensor)) + + if not urls: + return ({},) + + return ({"images": urls},) diff --git a/modules/textInferenceInputsVideos.py b/modules/textInferenceInputsVideos.py new file mode 100644 index 0000000..13680dd --- /dev/null +++ b/modules/textInferenceInputsVideos.py @@ -0,0 +1,51 @@ +"""Runware Text Inference Inputs Videos — builds inputs.videos (URL or mediaUUID per item).""" + +from typing import Any, Dict, List, Tuple + +from .utils import runwareUtils as rwUtils + + +class RunwareTextInferenceInputsVideos: + """Collect video references for textInference task.inputs.videos.""" + + _MAX_VIDEOS = 4 + + @classmethod + def INPUT_TYPES(cls): + optional: Dict[str, tuple] = { + "useVideos": ("BOOLEAN", { + "default": False, + "tooltip": "Enable to include inputs.videos in the text inference request.", + }), + } + for i in range(1, cls._MAX_VIDEOS + 1): + ordinal = rwUtils.getOrdinal(i) + optional[f"Video{i}"] = ("STRING", { + "default": "", + "tooltip": f"Video URL or mediaUUID for the {ordinal} video. Only used when 'Use Videos' is enabled.", + }) + return {"required": {}, "optional": optional} + + RETURN_TYPES = ("RUNWARETEXTINFERENCEINPUTSVIDEOS",) + RETURN_NAMES = ("videos",) + FUNCTION = "createVideos" + CATEGORY = "Runware/Text" + DESCRIPTION = ( + "Collect one or more video references (URL or mediaUUID) for Runware Text Inference " + "inputs.videos. Connect the output to Runware Text Inference Inputs." + ) + + def createVideos(self, **kwargs) -> Tuple[Dict[str, Any], ...]: + if not kwargs.get("useVideos", False): + return ({},) + + urls: List[str] = [] + for i in range(1, self._MAX_VIDEOS + 1): + raw = kwargs.get(f"Video{i}", "") + if raw is not None and str(raw).strip(): + urls.append(str(raw).strip()) + + if not urls: + return ({},) + + return ({"videos": urls},) diff --git a/modules/textInferenceSettings.py b/modules/textInferenceSettings.py index aed5a30..0737f61 100644 --- a/modules/textInferenceSettings.py +++ b/modules/textInferenceSettings.py @@ -70,9 +70,13 @@ class RunwareTextInferenceSettings: "tooltip": f"Top-k sampling (1–{cls._MAX_TOP_K}).", }), # Keep this in the original slot to avoid shifting legacy widgets_values. - "thinkingLevel": (["none", "low", "medium", "high"], { + "thinkingLevel": (["none", "low", "medium", "high", "max", "adaptive"], { "default": "none", - "tooltip": "Extended reasoning level: none, low, medium, high.", + "tooltip": ( + "Extended reasoning level: none, low, medium, high, max, adaptive. " + "off -> disabled. low/medium/high/max -> adaptive thinking with the chosen effort. " + "adaptive -> model picks budget." + ), }), "useStopSequences": ("BOOLEAN", { "default": False, @@ -123,6 +127,9 @@ class RunwareTextInferenceSettings: "default": "{}", "tooltip": "JSON object for toolChoice.", }), + "cache": ("RUNWARETEXTINFERENCESETTINGSCACHE", { + "tooltip": "Connect Runware Text Inference Settings Cache for settings.cache.{scope, ttl}.", + }), }, } @@ -132,7 +139,7 @@ class RunwareTextInferenceSettings: CATEGORY = "Runware/Text" DESCRIPTION = ( "Optional text inference settings (systemPrompt, maxTokens, temperature, topP, topK, " - "stopSequences, presencePenalty, frequencyPenalty, tools, toolChoice, thinkingLevel). " + "stopSequences, presencePenalty, frequencyPenalty, tools, toolChoice, thinkingLevel, cache). " "Connect to Runware Text Inference." ) @@ -191,7 +198,7 @@ class RunwareTextInferenceSettings: "minimal": "low", } value = aliases.get(value, value) - if value not in ("none", "low", "medium", "high"): + if value not in ("none", "low", "medium", "high", "max", "adaptive"): return "none" return value @@ -242,6 +249,10 @@ class RunwareTextInferenceSettings: if tl != "none": out["thinkingLevel"] = tl + cache_cfg = kwargs.get("cache") + if cache_cfg is not None and isinstance(cache_cfg, dict) and len(cache_cfg) > 0: + out["cache"] = cache_cfg + if not out: return ({},) diff --git a/modules/textInferenceSettingsCache.py b/modules/textInferenceSettingsCache.py new file mode 100644 index 0000000..f5b91b1 --- /dev/null +++ b/modules/textInferenceSettingsCache.py @@ -0,0 +1,64 @@ +""" +Runware Text Inference Settings Cache Node +Builds the cache object for Runware Text Inference (settings.cache.{scope, ttl}). + +Maps cache_control placement (Anthropic): + - scope: where cache_control is attached + * system -> system block only + * system+history -> system block + last user message block (default) + - ttl: cache lifetime (5m or 1h) +""" + +from typing import Dict, Any + + +class RunwareTextInferenceSettingsCache: + """Runware Text Inference Settings Cache node.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "scope": (["system", "system+history"], { + "default": "system+history", + "tooltip": ( + "Where cache_control is attached.\n" + "- system: cache the system block only.\n" + "- system+history: cache the system block and the last user message block." + ), + }), + "ttl": (["5m", "1h"], { + "default": "5m", + "tooltip": "Cache lifetime: 5m (5 minutes) or 1h (1 hour).", + }), + } + } + + RETURN_TYPES = ("RUNWARETEXTINFERENCESETTINGSCACHE",) + RETURN_NAMES = ("cache",) + FUNCTION = "createCache" + CATEGORY = "Runware/Text" + DESCRIPTION = ( + "Build a settings.cache object (scope + ttl) and connect it to Runware Text Inference. " + "Merged into the request as settings.cache.{scope, ttl}." + ) + + def createCache(self, **kwargs) -> tuple[Dict[str, Any]]: + scope = (kwargs.get("scope") or "system+history").strip().lower() + if scope not in ("system", "system+history"): + scope = "system+history" + + ttl = (kwargs.get("ttl") or "5m").strip().lower() + if ttl not in ("5m", "1h"): + ttl = "5m" + + return ({"scope": scope, "ttl": ttl},) + + +NODE_CLASS_MAPPINGS = { + "RunwareTextInferenceSettingsCache": RunwareTextInferenceSettingsCache, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "RunwareTextInferenceSettingsCache": "Runware Text Inference Settings Cache", +} diff --git a/modules/textInferenceToolChoice.py b/modules/textInferenceToolChoice.py new file mode 100644 index 0000000..3e33656 --- /dev/null +++ b/modules/textInferenceToolChoice.py @@ -0,0 +1,73 @@ +""" +Runware Text Inference Tool Choice Node +Builds the toolChoice object for Runware Text Inference (textInference). + +Strategies (type): + - auto: model decides whether to call a tool (recommended default) + - any: model must call at least one tool but chooses which one + - tool: model must call the specific tool identified by `name` + - none: model will not call any tool +""" + +from typing import Dict, Any + + +class RunwareTextInferenceToolChoice: + """Runware Text Inference Tool Choice node.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "type": (["auto", "any", "tool", "none"], { + "default": "auto", + "tooltip": ( + "Strategy the model uses to decide when and which tools to call.\n" + "- auto: model decides (recommended default).\n" + "- any: model must call at least one tool, of its choosing.\n" + "- tool: model must call the specific tool named below.\n" + "- none: model will not call any tool." + ), + }), + "name": ("STRING", { + "default": "", + "tooltip": "Name of the specific tool the model must call. Required when type is 'tool'; ignored otherwise.", + }), + } + } + + RETURN_TYPES = ("RUNWARETEXTINFERENCETOOLCHOICE",) + RETURN_NAMES = ("toolChoice",) + FUNCTION = "createToolChoice" + CATEGORY = "Runware/Text" + DESCRIPTION = ( + "Build a toolChoice object (type + optional tool name) and connect it to " + "Runware Text Inference. Required when type is 'tool'." + ) + + def createToolChoice(self, **kwargs) -> tuple[Dict[str, Any]]: + choice_type = (kwargs.get("type") or "auto").strip().lower() + if choice_type not in ("auto", "any", "tool", "none"): + choice_type = "auto" + + name = (kwargs.get("name") or "").strip() + + result: Dict[str, Any] = {"type": choice_type} + if choice_type == "tool": + if not name: + raise Exception( + "Tool Choice: 'name' is required when type is 'tool'. " + "Provide the exact tool name the model must call." + ) + result["name"] = name + + return (result,) + + +NODE_CLASS_MAPPINGS = { + "RunwareTextInferenceToolChoice": RunwareTextInferenceToolChoice, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "RunwareTextInferenceToolChoice": "Runware Text Inference Tool Choice", +} diff --git a/modules/textModelSearch.py b/modules/textModelSearch.py index d81e7ed..e66a3d0 100644 --- a/modules/textModelSearch.py +++ b/modules/textModelSearch.py @@ -13,6 +13,11 @@ class RunwareTextModelSearch: "openai:gpt@5.4-mini (GPT-5.4 Mini)", "openai:gpt@5.4-nano (GPT-5.4 Nano)", ], + "Anthropic": [ + "anthropic:claude@opus-4.7 (Claude Opus 4.7)", + "anthropic:claude@sonnet-4.6 (Claude Sonnet 4.6)", + "anthropic:claude@haiku-4.5 (Claude Haiku 4.5)", + ], "Google": [ "google:gemini@3-flash (Gemini 3 Flash)", ], @@ -23,6 +28,7 @@ class RunwareTextModelSearch: MODEL_PROVIDERS = [ "All", + "Anthropic", "Google", "MiniMax", "OpenAI", diff --git a/workflows/Runware_Text_Inference_Anthropic.json b/workflows/Runware_Text_Inference_Anthropic.json new file mode 100644 index 0000000..019f60a --- /dev/null +++ b/workflows/Runware_Text_Inference_Anthropic.json @@ -0,0 +1 @@ +{"id":"1f78f4f1-a1ae-47a1-af53-9b1aae2e4a79","revision":0,"last_node_id":15,"last_link_id":12,"nodes":[{"id":7,"type":"Runware Text Inference","pos":[443.9703710974516,-190.2246268986115],"size":[196.7369140625,126],"flags":{},"order":9,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"RUNWARETEXTMODEL","link":5},{"localized_name":"messages","name":"messages","type":"RUNWARETEXTINFERENCEMESSAGES","link":8},{"localized_name":"settings","name":"settings","shape":7,"type":"RUNWARETEXTINFERENCESETTINGS","link":7},{"localized_name":"providerSettings","name":"providerSettings","shape":7,"type":"RUNWAREPROVIDERSETTINGS","link":null},{"localized_name":"toolChoice","name":"toolChoice","shape":7,"type":"RUNWARETEXTINFERENCETOOLCHOICE","link":6},{"localized_name":"inputs","name":"inputs","shape":7,"type":"RUNWARETEXTINFERENCEINPUTS","link":11}],"outputs":[{"localized_name":"text","name":"text","type":"STRING","links":[4]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference"},"widgets_values":[],"bgcolor":"#5345bf"},{"id":4,"type":"Runware Text Inference Tool Choice","pos":[-29.744905656675527,30.964936780633753],"size":[339.18515625,82],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"type","name":"type","type":"COMBO","widget":{"name":"type"},"link":null},{"localized_name":"name","name":"name","type":"STRING","widget":{"name":"name"},"link":null}],"outputs":[{"localized_name":"toolChoice","name":"toolChoice","type":"RUNWARETEXTINFERENCETOOLCHOICE","links":[6]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Tool Choice"},"widgets_values":["auto",""],"bgcolor":"#5345bf"},{"id":5,"type":"Runware Text Inference Settings Cache","pos":[-67.48363833651044,201.55750753535082],"size":[361.0806640625,82],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"scope","name":"scope","type":"COMBO","widget":{"name":"scope"},"link":null},{"localized_name":"ttl","name":"ttl","type":"COMBO","widget":{"name":"ttl"},"link":null}],"outputs":[{"localized_name":"cache","name":"cache","type":"RUNWARETEXTINFERENCESETTINGSCACHE","links":[3]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Settings Cache"},"widgets_values":["system+history","5m"],"bgcolor":"#5345bf"},{"id":2,"type":"Runware Text Model Search","pos":[44.05757812870157,-203.29950897408327],"size":[270,130],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"Model Search","name":"Model Search","type":"STRING","widget":{"name":"Model Search"},"link":null},{"localized_name":"Model Provider","name":"Model Provider","type":"COMBO","widget":{"name":"Model Provider"},"link":null},{"localized_name":"TextList","name":"TextList","type":"COMBO","widget":{"name":"TextList"},"link":null},{"localized_name":"Use Search Value","name":"Use Search Value","type":"BOOLEAN","widget":{"name":"Use Search Value"},"link":null}],"outputs":[{"localized_name":"model","name":"model","type":"RUNWARETEXTMODEL","links":[5]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Model Search"},"widgets_values":["","Anthropic","anthropic:claude@opus-4.7 (Claude Opus 4.7)",false],"bgcolor":"#5345bf"},{"id":6,"type":"Runware Text Inference Settings","pos":[381.4308092607773,7.637991025916822],"size":[400,658],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"cache","name":"cache","shape":7,"type":"RUNWARETEXTINFERENCESETTINGSCACHE","link":3},{"localized_name":"useSystemPrompt","name":"useSystemPrompt","shape":7,"type":"BOOLEAN","widget":{"name":"useSystemPrompt"},"link":null},{"localized_name":"systemPrompt","name":"systemPrompt","shape":7,"type":"STRING","widget":{"name":"systemPrompt"},"link":null},{"localized_name":"useMaxTokens","name":"useMaxTokens","shape":7,"type":"BOOLEAN","widget":{"name":"useMaxTokens"},"link":null},{"localized_name":"maxTokens","name":"maxTokens","shape":7,"type":"INT","widget":{"name":"maxTokens"},"link":null},{"localized_name":"useTemperature","name":"useTemperature","shape":7,"type":"BOOLEAN","widget":{"name":"useTemperature"},"link":null},{"localized_name":"temperature","name":"temperature","shape":7,"type":"FLOAT","widget":{"name":"temperature"},"link":null},{"localized_name":"useTopP","name":"useTopP","shape":7,"type":"BOOLEAN","widget":{"name":"useTopP"},"link":null},{"localized_name":"topP","name":"topP","shape":7,"type":"FLOAT","widget":{"name":"topP"},"link":null},{"localized_name":"useTopK","name":"useTopK","shape":7,"type":"BOOLEAN","widget":{"name":"useTopK"},"link":null},{"localized_name":"topK","name":"topK","shape":7,"type":"INT","widget":{"name":"topK"},"link":null},{"localized_name":"thinkingLevel","name":"thinkingLevel","shape":7,"type":"COMBO","widget":{"name":"thinkingLevel"},"link":null},{"localized_name":"useStopSequences","name":"useStopSequences","shape":7,"type":"BOOLEAN","widget":{"name":"useStopSequences"},"link":null},{"localized_name":"stopSequences","name":"stopSequences","shape":7,"type":"STRING","widget":{"name":"stopSequences"},"link":null},{"localized_name":"usePresencePenalty","name":"usePresencePenalty","shape":7,"type":"BOOLEAN","widget":{"name":"usePresencePenalty"},"link":null},{"localized_name":"presencePenalty","name":"presencePenalty","shape":7,"type":"FLOAT","widget":{"name":"presencePenalty"},"link":null},{"localized_name":"useFrequencyPenalty","name":"useFrequencyPenalty","shape":7,"type":"BOOLEAN","widget":{"name":"useFrequencyPenalty"},"link":null},{"localized_name":"frequencyPenalty","name":"frequencyPenalty","shape":7,"type":"FLOAT","widget":{"name":"frequencyPenalty"},"link":null},{"localized_name":"useTools","name":"useTools","shape":7,"type":"BOOLEAN","widget":{"name":"useTools"},"link":null},{"localized_name":"tools","name":"tools","shape":7,"type":"STRING","widget":{"name":"tools"},"link":null},{"localized_name":"useToolChoice","name":"useToolChoice","shape":7,"type":"BOOLEAN","widget":{"name":"useToolChoice"},"link":null},{"localized_name":"toolChoice","name":"toolChoice","shape":7,"type":"STRING","widget":{"name":"toolChoice"},"link":null}],"outputs":[{"localized_name":"settings","name":"settings","type":"RUNWARETEXTINFERENCESETTINGS","links":[7]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Settings"},"widgets_values":[false,"",false,4096,false,1,false,1,false,40,"none",false,"",false,0,false,0,false,"[]",false,"{}"],"bgcolor":"#5345bf"},{"id":12,"type":"Runware Text Inference Inputs","pos":[33.68724167528191,384.2004910259165],"size":[241.3892578125,46],"flags":{},"order":8,"mode":0,"inputs":[{"localized_name":"images","name":"images","shape":7,"type":"RUNWARETEXTINFERENCEINPUTSIMAGES","link":10},{"localized_name":"videos","name":"videos","shape":7,"type":"RUNWARETEXTINFERENCEINPUTSVIDEOS","link":null}],"outputs":[{"localized_name":"inputs","name":"inputs","type":"RUNWARETEXTINFERENCEINPUTS","links":[11]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Inputs"},"widgets_values":[],"bgcolor":"#5345bf"},{"id":15,"type":"LoadImage","pos":[-437.4081684809681,-220.5091198231399],"size":[282.798828125,314.00000000000006],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[12]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.19.3","Node name for S&R":"LoadImage"},"widgets_values":[".webp","image"]},{"id":3,"type":"Runware Save Text","pos":[810.6171300154941,-206.42450897408324],"size":[514.4604952830189,291.61261792452825],"flags":{},"order":10,"mode":0,"inputs":[{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":4}],"outputs":[{"localized_name":"text","name":"text","type":"STRING","links":null}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Save Text"},"widgets_values":["This picture shows a young woman posing outdoors in what appears to be Paris, France. Here are the details:\n\n**The Subject:**\n- A young woman with dark hair styled in a messy updo or braid with wispy bangs framing her face\n- She's wearing soft, romantic makeup with reddish-pink eyeshadow and bold red-orange lipstick\n- Her outfit is a dusty pink/mauve blouse or dress with ruffled details, a scoop neckline, and long billowy sleeves\n- She has a brown leather bag strap visible over her shoulder\n- Her expression is calm and contemplative as she looks toward the camera\n\n**The Setting:**\n- She's leaning against a stone balustrade (railing) of what appears to be a bridge\n- In the background, you can see a classic Parisian stone arch bridge spanning a river (likely the Seine)\n- A grand Haussmann-style building with ornate architecture is visible on the right, which could be part of the Conciergerie or a similar historic Parisian landmark\n- The sky is overcast with soft, cloudy lighting\n- Some greenery/trees are visible on the left\n\nThe overall aesthetic is romantic, vintage, and editorial — reminiscent of a fashion or travel photoshoot with a dreamy, cinematic quality."],"bgcolor":"#5345bf"},{"id":8,"type":"Runware Text Inference Messages","pos":[826.051619254881,161.19783772402994],"size":[268.261328125,166],"flags":{},"order":7,"mode":0,"inputs":[{"localized_name":"Message Segment 1","name":"Message Segment 1","type":"RUNWARETEXTINFERENCEMESSAGE","link":9},{"localized_name":"Message Segment 2","name":"Message Segment 2","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null},{"localized_name":"Message Segment 3","name":"Message Segment 3","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null},{"localized_name":"Message Segment 4","name":"Message Segment 4","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null},{"localized_name":"Message Segment 5","name":"Message Segment 5","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null},{"localized_name":"Message Segment 6","name":"Message Segment 6","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null},{"localized_name":"Message Segment 7","name":"Message Segment 7","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null},{"localized_name":"Message Segment 8","name":"Message Segment 8","shape":7,"type":"RUNWARETEXTINFERENCEMESSAGE","link":null}],"outputs":[{"localized_name":"messages","name":"messages","type":"RUNWARETEXTINFERENCEMESSAGES","links":[8]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Messages"},"widgets_values":[],"bgcolor":"#5345bf"},{"id":9,"type":"Runware Text Inference Message","pos":[1146.8125899211548,185.7113990447846],"size":[400,200],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"role","name":"role","type":"COMBO","widget":{"name":"role"},"link":null},{"localized_name":"content","name":"content","type":"STRING","widget":{"name":"content"},"link":null}],"outputs":[{"localized_name":"message","name":"message","type":"RUNWARETEXTINFERENCEMESSAGE","links":[9]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Message"},"widgets_values":["user","What is in this pciture?"],"bgcolor":"#5345bf"},{"id":14,"type":"Runware Text Inference Inputs Images","pos":[-452.9457458689397,186.80957121459585],"size":[353.34921875,250],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"Image 1","name":"Image 1","shape":7,"type":"IMAGE","link":12},{"localized_name":"Image 2","name":"Image 2","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image 3","name":"Image 3","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image 4","name":"Image 4","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image 5","name":"Image 5","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image 6","name":"Image 6","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image 7","name":"Image 7","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image 8","name":"Image 8","shape":7,"type":"IMAGE","link":null},{"localized_name":"Image1","name":"Image1","shape":7,"type":"STRING","widget":{"name":"Image1"},"link":null},{"localized_name":"Image3","name":"Image3","shape":7,"type":"STRING","widget":{"name":"Image3"},"link":null},{"localized_name":"Image5","name":"Image5","shape":7,"type":"STRING","widget":{"name":"Image5"},"link":null},{"localized_name":"Image7","name":"Image7","shape":7,"type":"STRING","widget":{"name":"Image7"},"link":null}],"outputs":[{"localized_name":"images","name":"images","type":"RUNWARETEXTINFERENCEINPUTSIMAGES","links":[10]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"100644fdfb3f0f8a3aacea07992e7aa9e284f17a","Node name for S&R":"Runware Text Inference Inputs Images"},"widgets_values":[],"bgcolor":"#5345bf"}],"links":[[3,5,0,6,0,"RUNWARETEXTINFERENCESETTINGSCACHE"],[4,7,0,3,0,"STRING"],[5,2,0,7,0,"RUNWARETEXTMODEL"],[6,4,0,7,4,"RUNWARETEXTINFERENCETOOLCHOICE"],[7,6,0,7,2,"RUNWARETEXTINFERENCESETTINGS"],[8,8,0,7,1,"RUNWARETEXTINFERENCEMESSAGES"],[9,9,0,8,0,"RUNWARETEXTINFERENCEMESSAGE"],[10,14,0,12,0,"RUNWARETEXTINFERENCEINPUTSIMAGES"],[11,12,0,7,5,"RUNWARETEXTINFERENCEINPUTS"],[12,15,0,14,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.53,"offset":[625.2835185694114,492.4932000118191]}},"version":0.4} \ No newline at end of file