Add OpenRouter request timeout

This commit is contained in:
Julien D.
2026-05-14 11:05:50 +02:00
parent 72754d539a
commit a3bb67d5dc
4 changed files with 211 additions and 43 deletions
+69 -41
View File
@@ -26,9 +26,12 @@ class OpenRouterNode:
3) "Credits": a string showing your remaining OpenRouter account balance
"""
models_cache = None
last_fetch_time = 0
cache_duration = 3600 # Cache duration in seconds (1 hour)
models_cache = None
last_fetch_time = 0
cache_duration = 3600 # Cache duration in seconds (1 hour)
default_request_timeout = 120
min_request_timeout = 1
max_request_timeout = 3600
def __init__(self):
self.chat_manager = ChatSessionManager()
@@ -116,11 +119,18 @@ class OpenRouterNode:
"round": 0.01,
}),
"pdf_engine": (["auto", "mistral-ocr", "pdf-text"], {"default": "auto"}),
"chat_mode": ("BOOLEAN", {"default": False}),
},
"optional": {
"pdf_data": (PDF_DATA_TYPE,), # Use '*' and check structure in generate_response
"user_message_input": ("STRING", {"forceInput": True}),
"chat_mode": ("BOOLEAN", {"default": False}),
"request_timeout": ("INT", {
"default": cls.default_request_timeout,
"min": cls.min_request_timeout,
"max": cls.max_request_timeout,
"step": 1,
"display": "number",
}),
},
"optional": {
"pdf_data": (PDF_DATA_TYPE,), # Use '*' and check structure in generate_response
"user_message_input": ("STRING", {"forceInput": True}),
}
}
@@ -139,7 +149,7 @@ class OpenRouterNode:
if (cls.models_cache is None) or (current_time - cls.last_fetch_time > cls.cache_duration):
url = "https://openrouter.ai/api/v1/models"
try:
response = requests.get(url)
response = requests.get(url, timeout=cls.default_request_timeout)
response.raise_for_status()
models = response.json()["data"]
# Filter for models that support chat completions if needed, but API handles this
@@ -153,17 +163,27 @@ class OpenRouterNode:
cls.models_cache = ["error_fetching_models", "google/gemma-3-27b-it", "openai/gpt-4o"] # Example fallbacks
return cls.models_cache if cls.models_cache else ["error_fetching_models"] # Ensure it's never empty
def validate_temperature(self, temperature):
"""
Validates and converts temperature value to float within acceptable range.
"""
try:
def validate_temperature(self, temperature):
"""
Validates and converts temperature value to float within acceptable range.
"""
try:
temp = float(temperature)
return max(0.0, min(2.0, temp)) # Clamp between 0.0 and 2.0
except (ValueError, TypeError):
return 1.0 # Return default if conversion fails
def fetch_credits(self, api_key):
except (ValueError, TypeError):
return 1.0 # Return default if conversion fails
def validate_request_timeout(self, request_timeout):
"""
Validates and converts request timeout to seconds within an acceptable range.
"""
try:
timeout = int(request_timeout)
return max(self.min_request_timeout, min(self.max_request_timeout, timeout))
except (ValueError, TypeError):
return self.default_request_timeout
def fetch_credits(self, api_key, timeout=None):
"""
Fetches the user's credits information from the OpenRouter API.
Returns a formatted string with remaining credits.
@@ -180,8 +200,9 @@ class OpenRouterNode:
"X-Title": "ComfyUI OpenRouter LLM Node",
}
try:
response = requests.get(url, headers=headers)
try:
validated_timeout = self.validate_request_timeout(timeout)
response = requests.get(url, headers=headers, timeout=validated_timeout)
response.raise_for_status()
result = response.json()
@@ -205,10 +226,10 @@ class OpenRouterNode:
except json.JSONDecodeError:
return "Error fetching credits: Could not decode JSON response."
def generate_response(self, api_key, system_prompt, user_message_box, model,
web_search, cheapest, fastest, temperature, pdf_engine, chat_mode,
aspect_ratio="auto", image_resolution="1K", seed=0,
pdf_data=None, user_message_input=None, reasoning_effort="none", **kwargs):
def generate_response(self, api_key, system_prompt, user_message_box, model,
web_search, cheapest, fastest, temperature, pdf_engine, chat_mode,
request_timeout=120, aspect_ratio="auto", image_resolution="1K", seed=0,
pdf_data=None, user_message_input=None, reasoning_effort="none", **kwargs):
"""
Sends a completion request to the OpenRouter chat completion endpoint.
Handles text, optional image, and optional PDF inputs.
@@ -236,8 +257,9 @@ class OpenRouterNode:
"X-Title": "ComfyUI OpenRouter LLM Node",
}
# Validate and convert temperature
validated_temp = self.validate_temperature(temperature)
# Validate and convert temperature
validated_temp = self.validate_temperature(temperature)
validated_timeout = self.validate_request_timeout(request_timeout)
# Decide whether to use user_message_input or user_message_box
user_text = user_message_input if user_message_input is not None and user_message_input.strip() else user_message_box
@@ -389,10 +411,10 @@ class OpenRouterNode:
# --- Make API Call and Process Response ---
try:
start_time = time.time()
response = requests.post(url, headers=headers, json=data)
response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
end_time = time.time()
start_time = time.time()
response = requests.post(url, headers=headers, json=data, timeout=validated_timeout)
response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
end_time = time.time()
result = response.json()
# Debug: print truncated response to see what OpenRouter returned
@@ -487,7 +509,7 @@ class OpenRouterNode:
# Fetch credits information AFTER the main request
credits_text = self.fetch_credits(api_key)
credits_text = self.fetch_credits(api_key, timeout=validated_timeout)
# Save conversation in chat mode
if chat_mode and session_path:
@@ -625,11 +647,11 @@ class OpenRouterNode:
return max(1, round(len(text) / 4))
@classmethod
def IS_CHANGED(cls, api_key, system_prompt, user_message_box, model,
web_search, cheapest, fastest, temperature, pdf_engine, chat_mode,
aspect_ratio="auto", image_resolution="1K", seed=0,
pdf_data=None, user_message_input=None, **kwargs):
@classmethod
def IS_CHANGED(cls, api_key, system_prompt, user_message_box, model,
web_search, cheapest, fastest, temperature, pdf_engine, chat_mode,
request_timeout=120, aspect_ratio="auto", image_resolution="1K", seed=0,
pdf_data=None, user_message_input=None, **kwargs):
"""
Check if any input that affects the output has changed.
Includes hashing image and PDF data.
@@ -675,8 +697,14 @@ class OpenRouterNode:
try:
temp_float = float(temperature) if isinstance(temperature, (str, int, float)) else 1.0
temp_float = max(0.0, min(2.0, temp_float))
except (ValueError, TypeError):
temp_float = 1.0
except (ValueError, TypeError):
temp_float = 1.0
try:
timeout_int = int(request_timeout)
timeout_int = max(cls.min_request_timeout, min(cls.max_request_timeout, timeout_int))
except (ValueError, TypeError):
timeout_int = cls.default_request_timeout
# Combine all relevant inputs into a tuple for comparison
@@ -684,9 +712,9 @@ class OpenRouterNode:
# Note: api_key here is the UI value only. Keys resolved from the LLM_KEY
# env var or openrouter_api_key.json are intentionally NOT part of the
# cache key — they're treated as user environment, not workflow inputs.
return (api_key, system_prompt, user_message_box, model,
web_search, cheapest, fastest, temp_float, pdf_engine, chat_mode,
aspect_ratio, image_resolution, seed, tuple(image_hashes), pdf_hash, user_message_input)
return (api_key, system_prompt, user_message_box, model,
web_search, cheapest, fastest, temp_float, pdf_engine, chat_mode,
timeout_int, aspect_ratio, image_resolution, seed, tuple(image_hashes), pdf_hash, user_message_input)
# Node class mappings
NODE_CLASS_MAPPINGS = {