Files
MNeMoNiCuZ-ComfyUI-mnemic-n…/utils/api_utils.py
T
Claude 9d2a2b4cb9 Add Universal LLM API node; move all LLM secrets to .env
New node ✨🧠 Universal LLM API (MNeMiC_LLMAPI) talks to any LLM through
three protocols: OpenAI-compatible (OpenAI, Gemini, Grok, Groq, OpenRouter,
Mistral, DeepSeek, LM Studio, llama.cpp, vLLM…), Anthropic, and native
Ollama. Twelve endpoints are built in; users add their own in the
git-ignored nodes/llm/UserEndpoints.json.

Secrets never enter a workflow: the node stores only the endpoint and model
name. URLs, keys and headers are resolved on the backend from the endpoint
config and .env (${VAR} / ${VAR:-fallback}), keys never reach the browser,
and any key a server echoes back is masked in outputs, errors and logs.

Features: live streaming preview on the node, searchable model browser,
connection test, preset viewer, vision (image batch), reasoning control
mapped per provider, <think> separation into a thinking output, JSON mode,
retries with backoff, cancel mid-stream, automatic retry without parameters
an endpoint rejects, and Ollama keep_alive/num_ctx plus a ComfyUI VRAM free.

Shared infrastructure: utils/env_manager.py is now the single secret store
(hot-reloads .env, redaction, placeholder detection) and
utils/prompt_presets.py loads the presets the Groq nodes and the new node
share. The Groq nodes use both, so .env and preset edits apply without a
restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmQWqMnuUXU4XwF6xyaDyM
2026-09-25 19:51:33 +00:00

40 lines
1.9 KiB
Python

import requests
import json
import time
def make_api_request(data, headers, url, max_retries, console_log=False, timeout=120):
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=data, timeout=timeout)
if console_log:
print(f"Response status: {response.status_code}, Response body: {response.text}")
if response.status_code == 200:
try:
response_json = json.loads(response.text)
if 'choices' in response_json and response_json['choices']:
assistant_message = response_json['choices'][0]['message']['content']
if console_log:
print(f"Extracted message: {assistant_message}")
return assistant_message, True, "200 OK"
else:
return "No valid response content found.", False, "200 OK but no content"
except Exception as e:
print(f"Error parsing response: {str(e)}")
return "Error parsing JSON response.", False, "200 OK but failed to parse JSON"
else:
return "ERROR", False, f"{response.status_code} {response.reason}"
time.sleep(2)
return "Failed after all retries.", False, "Failed after all retries"
def load_prompt_options(prompt_files):
prompt_options = {}
for json_file in prompt_files:
try:
with open(json_file, 'r', encoding='utf-8') as file:
prompts = json.load(file)
prompt_options.update({prompt['name']: prompt['content'] for prompt in prompts})
except Exception as e:
print(f"Failed to load prompts from {json_file}: {str(e)}")
return prompt_options
def get_prompt_content(prompt_options, prompt_name):
return prompt_options.get(prompt_name, "No content found for selected prompt")