feat(nodes): refactor openai adapter and improve user experience

Motivation:
1. Clarify node classification by moving from 'OpenAI' to 'LLM' category.
2. Improve default prompt/system_prompt for better UX.
3. Resolve ambiguity in configuration passthrough from LLMs Loader node.

Implementation:
1. Updated CATEGORY to 🚦ComfyUI_LLMs_Toolkit/LLM.
2. Changed default system_prompt to English and set default prompt to 'hello'.
3. Added 'LLM_CONFIG (from input)' option to provider and model dropdowns to explicitly handle external configuration.
4. Fixed a bug where unresolved 'model' parameter was sent to the API instead of 'actual_model'.
This commit is contained in:
科林_Cyril Pilgrim
2026-03-03 11:20:18 +08:00
parent d21b704917
commit e5486eadfc
+33 -31
View File
@@ -108,12 +108,10 @@ class OpenAICompatibleLoader:
providers = _get_providers()
provider_names = [p['name'] for p in providers]
provider_names.append("Custom/手动输入")
provider_names.append("LLM_CONFIG (from input)")
# We need a massive list of models across all providers to satisfy comfyui's static dropdowns,
# but the UI will show/hide them based on provider selection (client-side).
# For simplicity in Python, we combine them all + Add Custom option.
all_models = ["Custom/手动输入"]
# Collect all models from providers for the dropdown
all_models = ["Custom/手动输入", "LLM_CONFIG (from input)"]
for p in providers:
for m in p.get("models", []):
if m not in all_models:
@@ -121,10 +119,10 @@ class OpenAICompatibleLoader:
return {
"required": {
"provider": (provider_names, {"default": provider_names[0] if provider_names else "Custom/手动输入"}),
"provider": (provider_names, {"default": provider_names[0] if provider_names else "LLM_CONFIG (from input)"}),
"model": (all_models, {"default": all_models[1] if len(all_models) > 1 else "Custom/手动输入"}),
"system_prompt": ("STRING", {"default": "你是一个 AI 助手", "multiline": True}),
"prompt": ("STRING", {"multiline": True, "default": ""}),
"system_prompt": ("STRING", {"default": "You are an AI assistant", "multiline": True}),
"prompt": ("STRING", {"multiline": True, "default": "hello"}),
},
"optional": {
"llm_config": ("LLM_CONFIG",), # Keep for backward compatibility
@@ -139,7 +137,7 @@ class OpenAICompatibleLoader:
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("response", "reasoning")
FUNCTION = "generate"
CATEGORY = "🚦ComfyUI_LLMs_Toolkit/OpenAI"
CATEGORY = "🚦ComfyUI_LLMs_Toolkit/LLM"
def __init__(self):
self.conversation_history: List[Dict[str, Any]] = []
@@ -219,8 +217,6 @@ class OpenAICompatibleLoader:
def _get_provider_config(self, provider_choice: str) -> Optional[Dict[str, Any]]:
"""Find the provider config by its dropdown label."""
if provider_choice == "Custom/手动输入":
return None
providers = _get_providers()
for p in providers:
@@ -233,8 +229,8 @@ class OpenAICompatibleLoader:
def generate(
self,
provider: str = "Custom/手动输入",
model: str = "Custom/手动输入",
provider: str = "",
model: str = "",
prompt: str = "",
system_prompt: Optional[str] = None,
llm_config: Optional[Dict[str, Any]] = None,
@@ -247,14 +243,28 @@ class OpenAICompatibleLoader:
"""Main generation entry point with graceful degradation."""
# ── Configuration Resolution ──────────────────────────────────
_FROM_INPUT = "LLM_CONFIG (from input)"
api_key = ""
base_url = ""
actual_model = model
actual_model = "" if model in ("Custom/手动输入", _FROM_INPUT, "") else model
provider_id = "custom"
provider_name = "Custom Endpoint"
# 1. New Provider Manager Configuration
if provider != "Custom/手动输入":
if provider == _FROM_INPUT:
# Mode 1: All config comes from LLM_CONFIG input node
if not llm_config:
return self._error_result(
'Provider is set to "LLM_CONFIG (from input)" but no LLM_CONFIG node is connected. '
'Please connect a LLMs Loader node or select a different provider.'
)
api_key = llm_config.get("api_key", "")
base_url = llm_config.get("base_url", "")
actual_model = llm_config.get("model", "") or actual_model
provider_id = llm_config.get("provider", "custom")
provider_name = provider_id
else:
# Mode 2: Config from Provider Manager (providers.json)
p_config = self._get_provider_config(provider)
if p_config:
provider_id = p_config["id"]
@@ -262,14 +272,6 @@ class OpenAICompatibleLoader:
api_key = p_config.get("apiKey", "") or api_key
base_url = p_config.get("apiHost", "") or base_url
# 2. Legacy fallback from LLM_CONFIG node if connected
if llm_config:
api_key = api_key or llm_config.get("api_key", "")
base_url = base_url or llm_config.get("base_url", "")
actual_model = actual_model or llm_config.get("model", "")
provider_id = llm_config.get("provider", provider_id)
provider_name = getattr(llm_config, "name", provider_id)
# ── Input validation (fail fast, don't waste API quota) ──────
if not prompt or not prompt.strip():
return self._error_result("输入的 Prompt 为空,请填写内容后重试")
@@ -300,9 +302,9 @@ class OpenAICompatibleLoader:
image_input = stripped
# ── Logging ──────────────────────────────────────────────────
print(f"{self.TAG} 使用配置: {provider_name} / {model}")
print(f"{self.TAG} 使用配置: {provider_name} / {actual_model}")
start = self._log_start(
provider_name, model, system_prompt, prompt, image_input
provider_name, actual_model, system_prompt, prompt, image_input
)
# ── Build messages ───────────────────────────────────────────
@@ -311,7 +313,7 @@ class OpenAICompatibleLoader:
messages = self._apply_memory(messages, enable_memory)
# ── o1/o3 System Role Downgrade (Compatibility) ──────────────
if re.search(r'o[1-3]', model):
if re.search(r'o[1-3]', actual_model):
for i, msg in enumerate(messages):
if msg["role"] == "system":
messages[i] = {"role": "user", "content": msg["content"]}
@@ -320,7 +322,7 @@ class OpenAICompatibleLoader:
# ── Build payload (clean, standard fields) ────────────
payload = {
"model": model,
"model": actual_model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
@@ -372,8 +374,8 @@ class OpenAICompatibleLoader:
except Exception as e:
elapsed_ms = int((time.time() - start) * 1000)
err = classify_error(e, provider_name, model, request_size_mb, elapsed_ms)
log_error(err, provider_name, model, request_size_mb, elapsed_ms)
err = classify_error(e, provider_name, actual_model, request_size_mb, elapsed_ms)
log_error(err, provider_name, actual_model, request_size_mb, elapsed_ms)
# Graceful degradation: return error text instead of crashing
return self._error_result(err.user_message())