mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 13:55:17 +08:00
* fix: route IBM WatsonX selections to model_id endpoint in unified get_llm A model selection sourced from the GET /api/v1/models catalog (which the frontend uses to augment the Language Model / Agent dropdown right after a provider is configured) carries only raw create_model_metadata fields — none of the enriched *_param keys that get_language_model_options injects. get_llm already derived model_class from the provider mapping for this case but still let model_name_param / api_key_param / url_param / project_id_param fall back to the generic defaults. For IBM WatsonX this is load-bearing: langchain_ibm.ChatWatsonx exposes both a `model` field (routes to the OpenAI-style Model Gateway, a different catalog) and a `model_id` field (routes to ModelInference, the foundation-models endpoint the dropdown is populated from). Falling back to the generic `model` sent the selected foundation-model id to the gateway, failing with "model <id> not found" / IAM "Provided user not found or active" — even though the dropdown, connection test, and standalone IBM watsonx.ai component all work. Derive all param names from get_provider_param_mapping(provider) when the selection metadata lacks them. Only WatsonX (model_id/apikey) differs from the generic defaults, so OpenAI/Anthropic/etc. are unaffected. Fixes #13671 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -71,8 +71,24 @@ def get_llm(
|
||||
provider = model.get("provider")
|
||||
metadata = model.get("metadata", {})
|
||||
|
||||
# Stored selections sourced from ``get_unified_models_detailed`` (e.g. the
|
||||
# ``GET /api/v1/models`` catalog the frontend uses to augment its dropdown
|
||||
# right after a provider is configured, before the backend repopulates
|
||||
# ``template[model]["options"]``) carry only the raw ``create_model_metadata``
|
||||
# fields — none of the enriched ``*_param`` keys. Derive those param names
|
||||
# from the provider mapping (the same source ``get_language_model_options``
|
||||
# uses) so provider-specific names are honored instead of the generic
|
||||
# ``model`` / ``api_key`` defaults. This matters for IBM WatsonX: passing a
|
||||
# foundation-model id under the generic ``model`` kwarg routes ChatWatsonx to
|
||||
# the Model Gateway (a different, OpenAI-style catalog), surfacing as
|
||||
# "model <id> not found" / IAM "Provided user not found or active" even
|
||||
# though the dropdown, connection test, and standalone component all work.
|
||||
from lfx.base.models.model_metadata import get_provider_param_mapping
|
||||
|
||||
provider_param_mapping = get_provider_param_mapping(provider) if provider else {}
|
||||
|
||||
# Get model class and parameter names from metadata
|
||||
api_key_param = metadata.get("api_key_param", "api_key")
|
||||
api_key_param = metadata.get("api_key_param") or provider_param_mapping.get("api_key_param", "api_key")
|
||||
|
||||
# Capture the user-supplied api_key BEFORE resolution so we can name
|
||||
# it back in the error message if it was a Global Variable reference
|
||||
@ -136,16 +152,16 @@ def get_llm(
|
||||
# carries the raw ``create_model_metadata`` fields, so we have to derive
|
||||
# ``model_class`` from the provider mapping that
|
||||
# ``get_language_model_options`` would have used.
|
||||
model_class_name = metadata.get("model_class")
|
||||
if not model_class_name and provider:
|
||||
from lfx.base.models.model_metadata import get_provider_param_mapping
|
||||
|
||||
model_class_name = get_provider_param_mapping(provider).get("model_class")
|
||||
model_class_name = metadata.get("model_class") or provider_param_mapping.get("model_class")
|
||||
if not model_class_name:
|
||||
msg = f"No model class defined for {model_name}"
|
||||
raise ValueError(msg)
|
||||
model_class = unified_models_module.get_model_class(model_class_name)
|
||||
model_name_param = metadata.get("model_name_param", "model")
|
||||
# The provider mapping stores the model-name param under ``model_param``
|
||||
# (``get_language_model_options`` re-keys it to ``model_name_param``); fall
|
||||
# back to it so e.g. IBM WatsonX resolves to ``model_id`` rather than the
|
||||
# generic ``model``.
|
||||
model_name_param = metadata.get("model_name_param") or provider_param_mapping.get("model_param", "model")
|
||||
|
||||
# Check if this is a reasoning model that doesn't support temperature
|
||||
reasoning_models = metadata.get("reasoning_models", [])
|
||||
@ -185,8 +201,10 @@ def get_llm(
|
||||
if provider in {"IBM WatsonX", "IBM watsonx.ai"}:
|
||||
# For watsonx, url and project_id are required parameters
|
||||
# Try database first, then component values, then environment variables
|
||||
url_param = metadata.get("url_param", "url")
|
||||
project_id_param = metadata.get("project_id_param", "project_id")
|
||||
url_param = metadata.get("url_param") or provider_param_mapping.get("url_param", "url")
|
||||
project_id_param = metadata.get("project_id_param") or provider_param_mapping.get(
|
||||
"project_id_param", "project_id"
|
||||
)
|
||||
|
||||
# Get all provider variables from database
|
||||
provider_vars = unified_models_module.get_all_variables_for_provider(user_id, provider)
|
||||
|
||||
111
src/lfx/tests/unit/base/models/test_get_llm_watsonx_params.py
Normal file
111
src/lfx/tests/unit/base/models/test_get_llm_watsonx_params.py
Normal file
@ -0,0 +1,111 @@
|
||||
"""Regression guards for IBM WatsonX param-name resolution in unified ``get_llm``.
|
||||
|
||||
A stored model selection sourced from the ``GET /api/v1/models`` catalog (which
|
||||
the frontend uses to augment the Language Model / Agent dropdown right after a
|
||||
provider is configured) carries only the raw ``create_model_metadata`` fields —
|
||||
none of the enriched ``*_param`` keys that ``get_language_model_options`` adds.
|
||||
|
||||
For IBM WatsonX this is load-bearing: ``langchain_ibm.ChatWatsonx`` exposes both
|
||||
a ``model`` field (routes to the OpenAI-style **Model Gateway**, a different
|
||||
catalog) and a ``model_id`` field (routes to **ModelInference**, the foundation
|
||||
models endpoint the dropdown is populated from). If ``get_llm`` falls back to the
|
||||
generic ``model`` param name, the selected foundation-model id is sent to the
|
||||
gateway and fails with "model <id> not found" / IAM "Provided user not found or
|
||||
active" — even though the dropdown, connection test, and standalone
|
||||
``IBM watsonx.ai`` component all work. See langflow-ai/langflow#13671.
|
||||
|
||||
These tests pin that ``get_llm`` resolves the provider-specific param names
|
||||
(``model_id``, ``apikey``, ``url``, ``project_id``) from the provider mapping
|
||||
even when the selection metadata is the raw catalog shape.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def _capture_factory():
|
||||
captured: dict = {}
|
||||
|
||||
class FakeChatModel:
|
||||
def __init__(self, **kwargs):
|
||||
captured.update(kwargs)
|
||||
|
||||
return FakeChatModel, captured
|
||||
|
||||
|
||||
def _raw_watsonx_selection() -> list[dict]:
|
||||
"""Selection as persisted from the raw ``/api/v1/models`` catalog.
|
||||
|
||||
Matches ``create_model_metadata`` output: provider/icon/flags only, with
|
||||
none of the enriched ``model_class`` / ``model_name_param`` / ``api_key_param``
|
||||
keys that ``get_language_model_options`` injects.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": "meta-llama/llama-3-3-70b-instruct",
|
||||
"provider": "IBM WatsonX",
|
||||
"metadata": {
|
||||
"provider": "IBM WatsonX",
|
||||
"icon": "IBM",
|
||||
"tool_calling": True,
|
||||
"model_type": "llm",
|
||||
"default": True,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _build_llm_from_raw_selection() -> dict:
|
||||
from lfx.base.models import unified_models as unified_models_module
|
||||
from lfx.base.models.unified_models.instantiation import get_llm
|
||||
|
||||
fake_cls, captured = _capture_factory()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
unified_models_module,
|
||||
"get_api_key_for_provider",
|
||||
return_value="ibm-apikey-xyz", # pragma: allowlist secret
|
||||
),
|
||||
patch.object(unified_models_module, "get_model_class", return_value=fake_cls),
|
||||
patch.object(
|
||||
unified_models_module,
|
||||
"get_all_variables_for_provider",
|
||||
return_value={
|
||||
"WATSONX_URL": "https://us-south.ml.cloud.ibm.com",
|
||||
"WATSONX_PROJECT_ID": "proj-123",
|
||||
},
|
||||
),
|
||||
):
|
||||
get_llm(_raw_watsonx_selection(), user_id=None, stream=True)
|
||||
|
||||
return captured
|
||||
|
||||
|
||||
def test_watsonx_raw_metadata_sends_model_id_not_model():
|
||||
"""The foundation-model id must be sent as ``model_id``, never the generic ``model``.
|
||||
|
||||
``model`` routes ChatWatsonx to the Model Gateway (different catalog) and is
|
||||
the root cause of the "model not found" failure in #13671.
|
||||
"""
|
||||
captured = _build_llm_from_raw_selection()
|
||||
|
||||
assert captured.get("model_id") == "meta-llama/llama-3-3-70b-instruct", (
|
||||
"get_llm must resolve IBM WatsonX's model param to 'model_id' even when the "
|
||||
f"selection carries only raw catalog metadata. Got kwargs keys: {sorted(captured)}."
|
||||
)
|
||||
assert "model" not in captured, (
|
||||
"Passing the foundation-model id under the generic 'model' kwarg routes "
|
||||
"ChatWatsonx to the Model Gateway (a different catalog), which fails with "
|
||||
f"'model not found'. Got kwargs keys: {sorted(captured)}."
|
||||
)
|
||||
|
||||
|
||||
def test_watsonx_raw_metadata_sends_apikey_and_connection_params():
|
||||
"""The api key, url, and project id must use WatsonX's native param names."""
|
||||
captured = _build_llm_from_raw_selection()
|
||||
|
||||
assert captured.get("apikey") == "ibm-apikey-xyz" # pragma: allowlist secret
|
||||
assert captured.get("url") == "https://us-south.ml.cloud.ibm.com"
|
||||
assert captured.get("project_id") == "proj-123"
|
||||
Reference in New Issue
Block a user