lfx-nightly 0.2.0.dev41__py3-none-any.whl → 0.3.0.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. lfx/__main__.py +137 -6
  2. lfx/_assets/component_index.json +1 -1
  3. lfx/base/agents/agent.py +10 -6
  4. lfx/base/agents/altk_base_agent.py +5 -3
  5. lfx/base/agents/altk_tool_wrappers.py +1 -1
  6. lfx/base/agents/events.py +1 -1
  7. lfx/base/agents/utils.py +4 -0
  8. lfx/base/composio/composio_base.py +78 -41
  9. lfx/base/data/cloud_storage_utils.py +156 -0
  10. lfx/base/data/docling_utils.py +130 -55
  11. lfx/base/datastax/astradb_base.py +75 -64
  12. lfx/base/embeddings/embeddings_class.py +113 -0
  13. lfx/base/models/__init__.py +11 -1
  14. lfx/base/models/google_generative_ai_constants.py +33 -9
  15. lfx/base/models/model_metadata.py +6 -0
  16. lfx/base/models/ollama_constants.py +196 -30
  17. lfx/base/models/openai_constants.py +37 -10
  18. lfx/base/models/unified_models.py +1123 -0
  19. lfx/base/models/watsonx_constants.py +43 -4
  20. lfx/base/prompts/api_utils.py +40 -5
  21. lfx/base/tools/component_tool.py +2 -9
  22. lfx/cli/__init__.py +10 -2
  23. lfx/cli/commands.py +3 -0
  24. lfx/cli/run.py +65 -409
  25. lfx/cli/script_loader.py +18 -7
  26. lfx/cli/validation.py +6 -3
  27. lfx/components/__init__.py +0 -3
  28. lfx/components/composio/github_composio.py +1 -1
  29. lfx/components/cuga/cuga_agent.py +39 -27
  30. lfx/components/data_source/api_request.py +4 -2
  31. lfx/components/datastax/astradb_assistant_manager.py +4 -2
  32. lfx/components/docling/__init__.py +45 -11
  33. lfx/components/docling/docling_inline.py +39 -49
  34. lfx/components/docling/docling_remote.py +1 -0
  35. lfx/components/elastic/opensearch_multimodal.py +1733 -0
  36. lfx/components/files_and_knowledge/file.py +384 -36
  37. lfx/components/files_and_knowledge/ingestion.py +8 -0
  38. lfx/components/files_and_knowledge/retrieval.py +10 -0
  39. lfx/components/files_and_knowledge/save_file.py +91 -88
  40. lfx/components/langchain_utilities/ibm_granite_handler.py +211 -0
  41. lfx/components/langchain_utilities/tool_calling.py +37 -6
  42. lfx/components/llm_operations/batch_run.py +64 -18
  43. lfx/components/llm_operations/lambda_filter.py +213 -101
  44. lfx/components/llm_operations/llm_conditional_router.py +39 -7
  45. lfx/components/llm_operations/structured_output.py +38 -12
  46. lfx/components/models/__init__.py +16 -74
  47. lfx/components/models_and_agents/agent.py +51 -203
  48. lfx/components/models_and_agents/embedding_model.py +171 -255
  49. lfx/components/models_and_agents/language_model.py +54 -318
  50. lfx/components/models_and_agents/mcp_component.py +96 -10
  51. lfx/components/models_and_agents/prompt.py +105 -18
  52. lfx/components/ollama/ollama_embeddings.py +111 -29
  53. lfx/components/openai/openai_chat_model.py +1 -1
  54. lfx/components/processing/text_operations.py +580 -0
  55. lfx/components/vllm/__init__.py +37 -0
  56. lfx/components/vllm/vllm.py +141 -0
  57. lfx/components/vllm/vllm_embeddings.py +110 -0
  58. lfx/custom/custom_component/component.py +65 -10
  59. lfx/custom/custom_component/custom_component.py +8 -6
  60. lfx/events/observability/__init__.py +0 -0
  61. lfx/events/observability/lifecycle_events.py +111 -0
  62. lfx/field_typing/__init__.py +57 -58
  63. lfx/graph/graph/base.py +40 -1
  64. lfx/graph/utils.py +109 -30
  65. lfx/graph/vertex/base.py +75 -23
  66. lfx/graph/vertex/vertex_types.py +0 -5
  67. lfx/inputs/__init__.py +2 -0
  68. lfx/inputs/input_mixin.py +55 -0
  69. lfx/inputs/inputs.py +120 -0
  70. lfx/interface/components.py +24 -7
  71. lfx/interface/initialize/loading.py +42 -12
  72. lfx/io/__init__.py +2 -0
  73. lfx/run/__init__.py +5 -0
  74. lfx/run/base.py +464 -0
  75. lfx/schema/__init__.py +50 -0
  76. lfx/schema/data.py +1 -1
  77. lfx/schema/image.py +26 -7
  78. lfx/schema/message.py +104 -11
  79. lfx/schema/workflow.py +171 -0
  80. lfx/services/deps.py +12 -0
  81. lfx/services/interfaces.py +43 -1
  82. lfx/services/mcp_composer/service.py +7 -1
  83. lfx/services/schema.py +1 -0
  84. lfx/services/settings/auth.py +95 -4
  85. lfx/services/settings/base.py +11 -1
  86. lfx/services/settings/constants.py +2 -0
  87. lfx/services/settings/utils.py +82 -0
  88. lfx/services/storage/local.py +13 -8
  89. lfx/services/transaction/__init__.py +5 -0
  90. lfx/services/transaction/service.py +35 -0
  91. lfx/tests/unit/components/__init__.py +0 -0
  92. lfx/utils/constants.py +2 -0
  93. lfx/utils/mustache_security.py +79 -0
  94. lfx/utils/validate_cloud.py +81 -3
  95. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/METADATA +7 -2
  96. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/RECORD +98 -80
  97. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/WHEEL +0 -0
  98. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,1123 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import lru_cache
4
+ from typing import TYPE_CHECKING, Any
5
+ from uuid import UUID
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Callable
9
+
10
+ import contextlib
11
+
12
+ from lfx.base.models.anthropic_constants import ANTHROPIC_MODELS_DETAILED
13
+ from lfx.base.models.google_generative_ai_constants import (
14
+ GOOGLE_GENERATIVE_AI_MODELS_DETAILED,
15
+ )
16
+ from lfx.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS_DETAILED, OLLAMA_MODELS_DETAILED
17
+ from lfx.base.models.openai_constants import OPENAI_EMBEDDING_MODELS_DETAILED, OPENAI_MODELS_DETAILED
18
+ from lfx.base.models.watsonx_constants import WATSONX_MODELS_DETAILED
19
+ from lfx.log.logger import logger
20
+ from lfx.services.deps import get_variable_service, session_scope
21
+ from lfx.utils.async_helpers import run_until_complete
22
+
23
+
24
+ @lru_cache(maxsize=1)
25
+ def get_model_classes():
26
+ """Lazy load model classes to avoid importing optional dependencies at module level."""
27
+ from langchain_anthropic import ChatAnthropic
28
+ from langchain_ibm import ChatWatsonx
29
+ from langchain_ollama import ChatOllama
30
+ from langchain_openai import ChatOpenAI
31
+
32
+ from lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed
33
+
34
+ return {
35
+ "ChatOpenAI": ChatOpenAI,
36
+ "ChatAnthropic": ChatAnthropic,
37
+ "ChatGoogleGenerativeAIFixed": ChatGoogleGenerativeAIFixed,
38
+ "ChatOllama": ChatOllama,
39
+ "ChatWatsonx": ChatWatsonx,
40
+ }
41
+
42
+
43
+ @lru_cache(maxsize=1)
44
+ def get_embedding_classes():
45
+ """Lazy load embedding classes to avoid importing optional dependencies at module level."""
46
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
47
+ from langchain_ibm import WatsonxEmbeddings
48
+ from langchain_ollama import OllamaEmbeddings
49
+ from langchain_openai import OpenAIEmbeddings
50
+
51
+ return {
52
+ "GoogleGenerativeAIEmbeddings": GoogleGenerativeAIEmbeddings,
53
+ "OpenAIEmbeddings": OpenAIEmbeddings,
54
+ "OllamaEmbeddings": OllamaEmbeddings,
55
+ "WatsonxEmbeddings": WatsonxEmbeddings,
56
+ }
57
+
58
+
59
+ @lru_cache(maxsize=1)
60
+ def get_model_provider_metadata():
61
+ return {
62
+ "OpenAI": {
63
+ "icon": "OpenAI",
64
+ "variable_name": "OPENAI_API_KEY",
65
+ },
66
+ "Anthropic": {
67
+ "icon": "Anthropic",
68
+ "variable_name": "ANTHROPIC_API_KEY",
69
+ },
70
+ "Google Generative AI": {
71
+ "icon": "GoogleGenerativeAI",
72
+ "variable_name": "GOOGLE_API_KEY",
73
+ },
74
+ "Ollama": {
75
+ "icon": "Ollama",
76
+ "variable_name": "OLLAMA_BASE_URL", # Ollama is local but can have custom URL
77
+ },
78
+ "IBM WatsonX": {
79
+ "icon": "WatsonxAI",
80
+ "variable_name": "WATSONX_APIKEY",
81
+ },
82
+ }
83
+
84
+
85
+ model_provider_metadata = get_model_provider_metadata()
86
+
87
+
88
+ @lru_cache(maxsize=1)
89
+ def get_models_detailed():
90
+ return [
91
+ ANTHROPIC_MODELS_DETAILED,
92
+ OPENAI_MODELS_DETAILED,
93
+ OPENAI_EMBEDDING_MODELS_DETAILED,
94
+ GOOGLE_GENERATIVE_AI_MODELS_DETAILED,
95
+ OLLAMA_MODELS_DETAILED,
96
+ OLLAMA_EMBEDDING_MODELS_DETAILED,
97
+ WATSONX_MODELS_DETAILED,
98
+ ]
99
+
100
+
101
+ MODELS_DETAILED = get_models_detailed()
102
+
103
+
104
+ @lru_cache(maxsize=1)
105
+ def get_model_provider_variable_mapping() -> dict[str, str]:
106
+ return {provider: meta["variable_name"] for provider, meta in model_provider_metadata.items()}
107
+
108
+
109
+ def get_model_providers() -> list[str]:
110
+ """Return a sorted list of unique provider names."""
111
+ return sorted({md.get("provider", "Unknown") for group in MODELS_DETAILED for md in group})
112
+
113
+
114
+ def get_unified_models_detailed(
115
+ providers: list[str] | None = None,
116
+ model_name: str | None = None,
117
+ model_type: str | None = None,
118
+ *,
119
+ include_unsupported: bool | None = None,
120
+ include_deprecated: bool | None = None,
121
+ only_defaults: bool = False,
122
+ **metadata_filters,
123
+ ):
124
+ """Return a list of providers and their models, optionally filtered.
125
+
126
+ Parameters
127
+ ----------
128
+ providers : list[str] | None
129
+ If given, only models from these providers are returned.
130
+ model_name : str | None
131
+ If given, only the model with this exact name is returned.
132
+ model_type : str | None
133
+ Optional. Restrict to models whose metadata "model_type" matches this value.
134
+ include_unsupported : bool
135
+ When False (default) models whose metadata contains ``not_supported=True``
136
+ are filtered out.
137
+ include_deprecated : bool
138
+ When False (default) models whose metadata contains ``deprecated=True``
139
+ are filtered out.
140
+ only_defaults : bool
141
+ When True, only models marked as default are returned.
142
+ The first 5 models from each provider (in list order) are automatically
143
+ marked as default. Defaults to False to maintain backward compatibility.
144
+ **metadata_filters
145
+ Arbitrary key/value pairs to match against the model's metadata.
146
+ Example: ``get_unified_models_detailed(size="4k", context_window=8192)``
147
+
148
+ Notes:
149
+ • Filtering is exact-match on the metadata values.
150
+ • If you *do* want to see unsupported models set ``include_unsupported=True``.
151
+ • If you *do* want to see deprecated models set ``include_deprecated=True``.
152
+ """
153
+ if include_unsupported is None:
154
+ include_unsupported = False
155
+ if include_deprecated is None:
156
+ include_deprecated = False
157
+
158
+ # Gather all models from imported *_MODELS_DETAILED lists
159
+ all_models: list[dict] = []
160
+ for models_detailed in MODELS_DETAILED:
161
+ all_models.extend(models_detailed)
162
+
163
+ # Apply filters
164
+ filtered_models: list[dict] = []
165
+ for md in all_models:
166
+ # Skip models flagged as not_supported unless explicitly included
167
+ if (not include_unsupported) and md.get("not_supported", False):
168
+ continue
169
+
170
+ # Skip models flagged as deprecated unless explicitly included
171
+ if (not include_deprecated) and md.get("deprecated", False):
172
+ continue
173
+
174
+ if providers and md.get("provider") not in providers:
175
+ continue
176
+ if model_name and md.get("name") != model_name:
177
+ continue
178
+ if model_type and md.get("model_type") != model_type:
179
+ continue
180
+ # Match arbitrary metadata key/value pairs
181
+ if any(md.get(k) != v for k, v in metadata_filters.items()):
182
+ continue
183
+
184
+ filtered_models.append(md)
185
+
186
+ # Group by provider
187
+ provider_map: dict[str, list[dict]] = {}
188
+ for metadata in filtered_models:
189
+ prov = metadata.get("provider", "Unknown")
190
+ provider_map.setdefault(prov, []).append(
191
+ {
192
+ "model_name": metadata.get("name"),
193
+ "metadata": {k: v for k, v in metadata.items() if k not in ("provider", "name")},
194
+ }
195
+ )
196
+
197
+ # Mark the first 5 models in each provider as default (based on list order)
198
+ # and optionally filter to only defaults
199
+ default_model_count = 5 # Number of default models per provider
200
+
201
+ for prov, models in provider_map.items():
202
+ for i, model in enumerate(models):
203
+ if i < default_model_count:
204
+ model["metadata"]["default"] = True
205
+ else:
206
+ model["metadata"]["default"] = False
207
+
208
+ # If only_defaults is True, filter to only default models
209
+ if only_defaults:
210
+ provider_map[prov] = [m for m in models if m["metadata"].get("default", False)]
211
+
212
+ # Format as requested
213
+ return [
214
+ {
215
+ "provider": prov,
216
+ "models": models,
217
+ "num_models": len(models),
218
+ **model_provider_metadata.get(prov, {}),
219
+ }
220
+ for prov, models in provider_map.items()
221
+ ]
222
+
223
+
224
+ def get_api_key_for_provider(user_id: UUID | str | None, provider: str, api_key: str | None = None) -> str | None:
225
+ """Get API key from self.api_key or global variables.
226
+
227
+ Args:
228
+ user_id: The user ID to look up global variables for
229
+ provider: The provider name (e.g., "OpenAI", "Anthropic")
230
+ api_key: An optional API key provided directly
231
+
232
+ Returns:
233
+ The API key if found, None otherwise
234
+ """
235
+ # First check if user provided an API key directly
236
+ if api_key:
237
+ return api_key
238
+
239
+ # If no user_id or user_id is the string "None", we can't look up global variables
240
+ if user_id is None or (isinstance(user_id, str) and user_id == "None"):
241
+ return None
242
+
243
+ # Map provider to global variable name
244
+ provider_variable_map = {
245
+ "OpenAI": "OPENAI_API_KEY",
246
+ "Anthropic": "ANTHROPIC_API_KEY",
247
+ "Google Generative AI": "GOOGLE_API_KEY",
248
+ "IBM WatsonX": "WATSONX_APIKEY",
249
+ }
250
+
251
+ variable_name = provider_variable_map.get(provider)
252
+ if not variable_name:
253
+ return None
254
+
255
+ # Try to get from global variables
256
+ async def _get_variable():
257
+ async with session_scope() as session:
258
+ variable_service = get_variable_service()
259
+ if variable_service is None:
260
+ return None
261
+ return await variable_service.get_variable(
262
+ user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
263
+ name=variable_name,
264
+ field="",
265
+ session=session,
266
+ )
267
+
268
+ return run_until_complete(_get_variable())
269
+
270
+
271
+ def validate_model_provider_key(variable_name: str, api_key: str) -> None:
272
+ """Validate a model provider API key by making a minimal test call.
273
+
274
+ Args:
275
+ variable_name: The variable name (e.g., OPENAI_API_KEY)
276
+ api_key: The API key to validate
277
+
278
+ Raises:
279
+ HTTPException: If the API key is invalid
280
+ """
281
+ # Map variable names to providers
282
+ provider_map = {
283
+ "OPENAI_API_KEY": "OpenAI",
284
+ "ANTHROPIC_API_KEY": "Anthropic",
285
+ "GOOGLE_API_KEY": "Google Generative AI",
286
+ "WATSONX_APIKEY": "IBM WatsonX",
287
+ "OLLAMA_BASE_URL": "Ollama",
288
+ }
289
+
290
+ provider = provider_map.get(variable_name)
291
+ if not provider:
292
+ return # Not a model provider key we validate
293
+
294
+ # Get the first available model for this provider
295
+ try:
296
+ models = get_unified_models_detailed(providers=[provider])
297
+ if not models or not models[0].get("models"):
298
+ return # No models available, skip validation
299
+
300
+ first_model = models[0]["models"][0]["model_name"]
301
+ except Exception: # noqa: BLE001
302
+ return # Can't get models, skip validation
303
+
304
+ # Test the API key based on provider
305
+ try:
306
+ if provider == "OpenAI":
307
+ from langchain_openai import ChatOpenAI
308
+
309
+ llm = ChatOpenAI(api_key=api_key, model_name=first_model, max_tokens=1)
310
+ llm.invoke("test")
311
+ elif provider == "Anthropic":
312
+ from langchain_anthropic import ChatAnthropic
313
+
314
+ llm = ChatAnthropic(anthropic_api_key=api_key, model=first_model, max_tokens=1)
315
+ llm.invoke("test")
316
+ elif provider == "Google Generative AI":
317
+ from langchain_google_genai import ChatGoogleGenerativeAI
318
+
319
+ llm = ChatGoogleGenerativeAI(google_api_key=api_key, model=first_model, max_tokens=1)
320
+ llm.invoke("test")
321
+ elif provider == "IBM WatsonX":
322
+ # WatsonX validation would require additional parameters
323
+ # Skip for now as it needs project_id, url, etc.
324
+ return
325
+ elif provider == "Ollama":
326
+ # Ollama is local, just verify the URL is accessible
327
+ import requests
328
+
329
+ response = requests.get(f"{api_key}/api/tags", timeout=5)
330
+ if response.status_code != requests.codes.ok:
331
+ msg = "Invalid Ollama base URL"
332
+ raise ValueError(msg)
333
+ except ValueError:
334
+ # Re-raise ValueError (validation failed)
335
+ raise
336
+ except Exception as e:
337
+ error_msg = str(e)
338
+ if "401" in error_msg or "authentication" in error_msg.lower() or "api key" in error_msg.lower():
339
+ msg = f"Invalid API key for {provider}"
340
+ raise ValueError(msg) from e
341
+ # For other errors, we'll allow the key to be saved (might be network issues, etc.)
342
+ return
343
+
344
+
345
+ def get_language_model_options(
346
+ user_id: UUID | str | None = None, *, tool_calling: bool | None = None
347
+ ) -> list[dict[str, Any]]:
348
+ """Return a list of available language model providers with their configuration.
349
+
350
+ This function uses get_unified_models_detailed() which respects the enabled/disabled
351
+ status from the settings page and automatically filters out deprecated/unsupported models.
352
+
353
+ Args:
354
+ user_id: Optional user ID to filter by user-specific enabled/disabled models
355
+ tool_calling: If True, only return models that support tool calling.
356
+ If False, only return models that don't support tool calling.
357
+ If None (default), return all models regardless of tool calling support.
358
+ """
359
+ # Get all LLM models (excluding embeddings, deprecated, and unsupported by default)
360
+ # Apply tool_calling filter if specified
361
+ if tool_calling is not None:
362
+ all_models = get_unified_models_detailed(
363
+ model_type="llm",
364
+ include_deprecated=False,
365
+ include_unsupported=False,
366
+ tool_calling=tool_calling,
367
+ )
368
+ else:
369
+ all_models = get_unified_models_detailed(
370
+ model_type="llm",
371
+ include_deprecated=False,
372
+ include_unsupported=False,
373
+ )
374
+
375
+ # Get disabled and explicitly enabled models for this user if user_id is provided
376
+ disabled_models = set()
377
+ explicitly_enabled_models = set()
378
+ if user_id:
379
+ try:
380
+
381
+ async def _get_model_status():
382
+ async with session_scope() as session:
383
+ variable_service = get_variable_service()
384
+ if variable_service is None:
385
+ return set(), set()
386
+ from langflow.services.variable.service import DatabaseVariableService
387
+
388
+ if not isinstance(variable_service, DatabaseVariableService):
389
+ return set(), set()
390
+ all_vars = await variable_service.get_all(
391
+ user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
392
+ session=session,
393
+ )
394
+ disabled = set()
395
+ enabled = set()
396
+ import json
397
+
398
+ for var in all_vars:
399
+ if var.name == "__disabled_models__" and var.value:
400
+ with contextlib.suppress(json.JSONDecodeError, TypeError):
401
+ disabled = set(json.loads(var.value))
402
+ elif var.name == "__enabled_models__" and var.value:
403
+ with contextlib.suppress(json.JSONDecodeError, TypeError):
404
+ enabled = set(json.loads(var.value))
405
+ return disabled, enabled
406
+
407
+ disabled_models, explicitly_enabled_models = run_until_complete(_get_model_status())
408
+ except Exception: # noqa: BLE001, S110
409
+ # If we can't get model status, continue without filtering
410
+ pass
411
+
412
+ # Get enabled providers (those with credentials configured)
413
+ enabled_providers = set()
414
+ if user_id:
415
+ try:
416
+
417
+ async def _get_enabled_providers():
418
+ async with session_scope() as session:
419
+ variable_service = get_variable_service()
420
+ if variable_service is None:
421
+ return set()
422
+ from langflow.services.variable.constants import CREDENTIAL_TYPE
423
+ from langflow.services.variable.service import DatabaseVariableService
424
+
425
+ if not isinstance(variable_service, DatabaseVariableService):
426
+ return set()
427
+ all_vars = await variable_service.get_all(
428
+ user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
429
+ session=session,
430
+ )
431
+ credential_names = {var.name for var in all_vars if var.type == CREDENTIAL_TYPE}
432
+ provider_variable_map = get_model_provider_variable_mapping()
433
+ return {
434
+ provider for provider, var_name in provider_variable_map.items() if var_name in credential_names
435
+ }
436
+
437
+ enabled_providers = run_until_complete(_get_enabled_providers())
438
+ except Exception: # noqa: BLE001, S110
439
+ # If we can't get enabled providers, show all
440
+ pass
441
+
442
+ options = []
443
+ model_class_mapping = {
444
+ "OpenAI": "ChatOpenAI",
445
+ "Anthropic": "ChatAnthropic",
446
+ "Google Generative AI": "ChatGoogleGenerativeAIFixed",
447
+ "Ollama": "ChatOllama",
448
+ "IBM WatsonX": "ChatWatsonx",
449
+ }
450
+
451
+ api_key_param_mapping = {
452
+ "OpenAI": "api_key",
453
+ "Anthropic": "api_key",
454
+ "Google Generative AI": "google_api_key",
455
+ "Ollama": "base_url",
456
+ "IBM WatsonX": "apikey",
457
+ }
458
+
459
+ # Track which providers have models
460
+ providers_with_models = set()
461
+
462
+ for provider_data in all_models:
463
+ provider = provider_data.get("provider")
464
+ models = provider_data.get("models", [])
465
+ icon = provider_data.get("icon", "Bot")
466
+
467
+ # Check if provider is enabled
468
+ is_provider_enabled = not user_id or not enabled_providers or provider in enabled_providers
469
+
470
+ # Track this provider
471
+ if is_provider_enabled:
472
+ providers_with_models.add(provider)
473
+
474
+ # Skip provider if user_id is provided and provider is not enabled
475
+ if user_id and enabled_providers and provider not in enabled_providers:
476
+ continue
477
+
478
+ for model_data in models:
479
+ model_name = model_data.get("model_name")
480
+ metadata = model_data.get("metadata", {})
481
+ is_default = metadata.get("default", False)
482
+
483
+ # Determine if model should be shown:
484
+ # - If not default and not explicitly enabled, skip it
485
+ # - If in disabled list, skip it
486
+ # - Otherwise, show it
487
+ if not is_default and model_name not in explicitly_enabled_models:
488
+ continue
489
+ if model_name in disabled_models:
490
+ continue
491
+
492
+ # Build the option dict
493
+ option = {
494
+ "name": model_name,
495
+ "icon": icon,
496
+ "category": provider,
497
+ "provider": provider,
498
+ "metadata": {
499
+ "context_length": 128000, # Default, can be overridden
500
+ "model_class": model_class_mapping.get(provider, "ChatOpenAI"),
501
+ "model_name_param": "model",
502
+ "api_key_param": api_key_param_mapping.get(provider, "api_key"),
503
+ },
504
+ }
505
+
506
+ # Add reasoning models list for OpenAI
507
+ if provider == "OpenAI" and metadata.get("reasoning"):
508
+ if "reasoning_models" not in option["metadata"]:
509
+ option["metadata"]["reasoning_models"] = []
510
+ option["metadata"]["reasoning_models"].append(model_name)
511
+
512
+ # Add base_url_param for Ollama
513
+ if provider == "Ollama":
514
+ option["metadata"]["base_url_param"] = "base_url"
515
+
516
+ # Add extra params for WatsonX
517
+ if provider == "IBM WatsonX":
518
+ option["metadata"]["model_name_param"] = "model_id"
519
+ option["metadata"]["url_param"] = "url"
520
+ option["metadata"]["project_id_param"] = "project_id"
521
+
522
+ options.append(option)
523
+
524
+ # Add disabled providers (providers that exist in metadata but have no enabled models)
525
+ if user_id:
526
+ for provider, metadata in model_provider_metadata.items():
527
+ if provider not in providers_with_models:
528
+ # This provider has no enabled models, add it as a disabled provider entry
529
+ options.append(
530
+ {
531
+ "name": f"__enable_provider_{provider}__",
532
+ "icon": metadata.get("icon", "Bot"),
533
+ "category": provider,
534
+ "provider": provider,
535
+ "metadata": {
536
+ "is_disabled_provider": True,
537
+ "variable_name": metadata.get("variable_name"),
538
+ },
539
+ }
540
+ )
541
+
542
+ return options
543
+
544
+
545
+ def get_embedding_model_options(user_id: UUID | str | None = None) -> list[dict[str, Any]]:
546
+ """Return a list of available embedding model providers with their configuration.
547
+
548
+ This function uses get_unified_models_detailed() which respects the enabled/disabled
549
+ status from the settings page and automatically filters out deprecated/unsupported models.
550
+
551
+ Args:
552
+ user_id: Optional user ID to filter by user-specific enabled/disabled models
553
+ """
554
+ # Get all embedding models (excluding deprecated and unsupported by default)
555
+ all_models = get_unified_models_detailed(
556
+ model_type="embeddings",
557
+ include_deprecated=False,
558
+ include_unsupported=False,
559
+ )
560
+
561
+ # Get disabled and explicitly enabled models for this user if user_id is provided
562
+ disabled_models = set()
563
+ explicitly_enabled_models = set()
564
+ if user_id:
565
+ try:
566
+
567
+ async def _get_model_status():
568
+ async with session_scope() as session:
569
+ variable_service = get_variable_service()
570
+ if variable_service is None:
571
+ return set(), set()
572
+ from langflow.services.variable.service import DatabaseVariableService
573
+
574
+ if not isinstance(variable_service, DatabaseVariableService):
575
+ return set(), set()
576
+ all_vars = await variable_service.get_all(
577
+ user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
578
+ session=session,
579
+ )
580
+ disabled = set()
581
+ enabled = set()
582
+ import json
583
+
584
+ for var in all_vars:
585
+ if var.name == "__disabled_models__" and var.value:
586
+ with contextlib.suppress(json.JSONDecodeError, TypeError):
587
+ disabled = set(json.loads(var.value))
588
+ elif var.name == "__enabled_models__" and var.value:
589
+ with contextlib.suppress(json.JSONDecodeError, TypeError):
590
+ enabled = set(json.loads(var.value))
591
+ return disabled, enabled
592
+
593
+ disabled_models, explicitly_enabled_models = run_until_complete(_get_model_status())
594
+ except Exception: # noqa: BLE001, S110
595
+ # If we can't get model status, continue without filtering
596
+ pass
597
+
598
+ # Get enabled providers (those with credentials configured)
599
+ enabled_providers = set()
600
+ if user_id:
601
+ try:
602
+
603
+ async def _get_enabled_providers():
604
+ async with session_scope() as session:
605
+ variable_service = get_variable_service()
606
+ if variable_service is None:
607
+ return set()
608
+ from langflow.services.variable.constants import CREDENTIAL_TYPE
609
+ from langflow.services.variable.service import DatabaseVariableService
610
+
611
+ if not isinstance(variable_service, DatabaseVariableService):
612
+ return set()
613
+ all_vars = await variable_service.get_all(
614
+ user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
615
+ session=session,
616
+ )
617
+ credential_names = {var.name for var in all_vars if var.type == CREDENTIAL_TYPE}
618
+ provider_variable_map = get_model_provider_variable_mapping()
619
+ return {
620
+ provider for provider, var_name in provider_variable_map.items() if var_name in credential_names
621
+ }
622
+
623
+ enabled_providers = run_until_complete(_get_enabled_providers())
624
+ except Exception: # noqa: BLE001, S110
625
+ # If we can't get enabled providers, show all
626
+ pass
627
+
628
+ options = []
629
+ embedding_class_mapping = {
630
+ "OpenAI": "OpenAIEmbeddings",
631
+ "Google Generative AI": "GoogleGenerativeAIEmbeddings",
632
+ "Ollama": "OllamaEmbeddings",
633
+ "IBM WatsonX": "WatsonxEmbeddings",
634
+ }
635
+
636
+ # Provider-specific param mappings
637
+ param_mappings = {
638
+ "OpenAI": {
639
+ "model": "model",
640
+ "api_key": "api_key",
641
+ "api_base": "base_url",
642
+ "dimensions": "dimensions",
643
+ "chunk_size": "chunk_size",
644
+ "request_timeout": "timeout",
645
+ "max_retries": "max_retries",
646
+ "show_progress_bar": "show_progress_bar",
647
+ "model_kwargs": "model_kwargs",
648
+ },
649
+ "Google Generative AI": {
650
+ "model": "model",
651
+ "api_key": "google_api_key",
652
+ "request_timeout": "request_options",
653
+ "model_kwargs": "client_options",
654
+ },
655
+ "Ollama": {
656
+ "model": "model",
657
+ "base_url": "base_url",
658
+ "num_ctx": "num_ctx",
659
+ "request_timeout": "request_timeout",
660
+ "model_kwargs": "model_kwargs",
661
+ },
662
+ "IBM WatsonX": {
663
+ "model_id": "model_id",
664
+ "url": "url",
665
+ "api_key": "apikey",
666
+ "project_id": "project_id",
667
+ "space_id": "space_id",
668
+ "request_timeout": "request_timeout",
669
+ },
670
+ }
671
+
672
+ # Track which providers have models
673
+ providers_with_models = set()
674
+
675
+ for provider_data in all_models:
676
+ provider = provider_data.get("provider")
677
+ models = provider_data.get("models", [])
678
+ icon = provider_data.get("icon", "Bot")
679
+
680
+ # Check if provider is enabled
681
+ is_provider_enabled = not user_id or not enabled_providers or provider in enabled_providers
682
+
683
+ # Track this provider
684
+ if is_provider_enabled:
685
+ providers_with_models.add(provider)
686
+
687
+ # Skip provider if user_id is provided and provider is not enabled
688
+ if user_id and enabled_providers and provider not in enabled_providers:
689
+ continue
690
+
691
+ for model_data in models:
692
+ model_name = model_data.get("model_name")
693
+ metadata = model_data.get("metadata", {})
694
+ is_default = metadata.get("default", False)
695
+
696
+ # Determine if model should be shown:
697
+ # - If not default and not explicitly enabled, skip it
698
+ # - If in disabled list, skip it
699
+ # - Otherwise, show it
700
+ if not is_default and model_name not in explicitly_enabled_models:
701
+ continue
702
+ if model_name in disabled_models:
703
+ continue
704
+
705
+ # Build the option dict
706
+ option = {
707
+ "name": model_name,
708
+ "icon": icon,
709
+ "category": provider,
710
+ "provider": provider,
711
+ "metadata": {
712
+ "embedding_class": embedding_class_mapping.get(provider, "OpenAIEmbeddings"),
713
+ "param_mapping": param_mappings.get(provider, param_mappings["OpenAI"]),
714
+ "model_type": "embeddings", # Mark as embedding model
715
+ },
716
+ }
717
+
718
+ options.append(option)
719
+
720
+ # Add disabled providers (providers that exist in metadata but have no enabled models)
721
+ if user_id:
722
+ for provider, metadata in model_provider_metadata.items():
723
+ if provider not in providers_with_models and provider in embedding_class_mapping:
724
+ # This provider has no enabled models and supports embeddings, add it as a disabled provider entry
725
+ options.append(
726
+ {
727
+ "name": f"__enable_provider_{provider}__",
728
+ "icon": metadata.get("icon", "Bot"),
729
+ "category": provider,
730
+ "provider": provider,
731
+ "metadata": {
732
+ "is_disabled_provider": True,
733
+ "variable_name": metadata.get("variable_name"),
734
+ },
735
+ }
736
+ )
737
+
738
+ return options
739
+
740
+
741
+ def normalize_model_names_to_dicts(model_names: list[str] | str) -> list[dict[str, Any]]:
742
+ """Convert simple model name(s) to list of dicts format.
743
+
744
+ Args:
745
+ model_names: A string or list of strings representing model names
746
+
747
+ Returns:
748
+ A list of dicts with full model metadata including runtime info
749
+
750
+ Examples:
751
+ >>> normalize_model_names_to_dicts('gpt-4o')
752
+ [{'name': 'gpt-4o', 'provider': 'OpenAI', 'metadata': {'model_class': 'ChatOpenAI', ...}}]
753
+
754
+ >>> normalize_model_names_to_dicts(['gpt-4o', 'claude-3'])
755
+ [{'name': 'gpt-4o', ...}, {'name': 'claude-3', ...}]
756
+ """
757
+ # Convert single string to list
758
+ if isinstance(model_names, str):
759
+ model_names = [model_names]
760
+
761
+ # Get all available models to look up metadata
762
+ try:
763
+ all_models = get_unified_models_detailed()
764
+ except Exception: # noqa: BLE001
765
+ # If we can't get models, just create basic dicts
766
+ return [{"name": name} for name in model_names]
767
+
768
+ # Model class mapping for runtime metadata
769
+ model_class_mapping = {
770
+ "OpenAI": "ChatOpenAI",
771
+ "Anthropic": "ChatAnthropic",
772
+ "Google Generative AI": "ChatGoogleGenerativeAIFixed",
773
+ "Ollama": "ChatOllama",
774
+ "IBM WatsonX": "ChatWatsonx",
775
+ }
776
+
777
+ api_key_param_mapping = {
778
+ "OpenAI": "api_key",
779
+ "Anthropic": "api_key",
780
+ "Google Generative AI": "google_api_key",
781
+ "Ollama": "base_url",
782
+ "IBM WatsonX": "apikey",
783
+ }
784
+
785
+ # Build a lookup map of model_name -> full model data with runtime metadata
786
+ model_lookup = {}
787
+ for provider_data in all_models:
788
+ provider = provider_data.get("provider")
789
+ icon = provider_data.get("icon", "Bot")
790
+ for model_data in provider_data.get("models", []):
791
+ model_name = model_data.get("model_name")
792
+ base_metadata = model_data.get("metadata", {})
793
+
794
+ # Build runtime metadata similar to get_language_model_options
795
+ runtime_metadata = {
796
+ "context_length": 128000, # Default
797
+ "model_class": model_class_mapping.get(provider, "ChatOpenAI"),
798
+ "model_name_param": "model",
799
+ "api_key_param": api_key_param_mapping.get(provider, "api_key"),
800
+ }
801
+
802
+ # Add reasoning models list for OpenAI
803
+ if provider == "OpenAI" and base_metadata.get("reasoning"):
804
+ runtime_metadata["reasoning_models"] = [model_name]
805
+
806
+ # Add base_url_param for Ollama
807
+ if provider == "Ollama":
808
+ runtime_metadata["base_url_param"] = "base_url"
809
+
810
+ # Add extra params for WatsonX
811
+ if provider == "IBM WatsonX":
812
+ runtime_metadata["model_name_param"] = "model_id"
813
+ runtime_metadata["url_param"] = "url"
814
+ runtime_metadata["project_id_param"] = "project_id"
815
+
816
+ # Merge base metadata with runtime metadata
817
+ full_metadata = {**base_metadata, **runtime_metadata}
818
+
819
+ model_lookup[model_name] = {
820
+ "name": model_name,
821
+ "icon": icon,
822
+ "category": provider,
823
+ "provider": provider,
824
+ "metadata": full_metadata,
825
+ }
826
+
827
+ # Convert string list to dict list
828
+ result = []
829
+ for name in model_names:
830
+ if name in model_lookup:
831
+ result.append(model_lookup[name])
832
+ else:
833
+ # Model not found in registry, create basic entry with minimal required metadata
834
+ result.append(
835
+ {
836
+ "name": name,
837
+ "provider": "Unknown",
838
+ "metadata": {
839
+ "model_class": "ChatOpenAI", # Default fallback
840
+ "model_name_param": "model",
841
+ "api_key_param": "api_key",
842
+ },
843
+ }
844
+ )
845
+
846
+ return result
847
+
848
+
849
+ def get_llm(
850
+ model,
851
+ user_id: UUID | str | None,
852
+ api_key=None,
853
+ temperature=None,
854
+ *,
855
+ stream=False,
856
+ watsonx_url=None,
857
+ watsonx_project_id=None,
858
+ ollama_base_url=None,
859
+ ) -> Any:
860
+ # Check if model is already a BaseLanguageModel instance (from a connection)
861
+ try:
862
+ from langchain_core.language_models import BaseLanguageModel
863
+
864
+ if isinstance(model, BaseLanguageModel):
865
+ # Model is already instantiated, return it directly
866
+ return model
867
+ except ImportError:
868
+ pass
869
+
870
+ # Safely extract model configuration
871
+ if not model or not isinstance(model, list) or len(model) == 0:
872
+ msg = "A model selection is required"
873
+ raise ValueError(msg)
874
+
875
+ # Extract the first model (only one expected)
876
+ model = model[0]
877
+
878
+ # Extract model configuration from metadata
879
+ model_name = model.get("name")
880
+ provider = model.get("provider")
881
+ metadata = model.get("metadata", {})
882
+
883
+ # Get model class and parameter names from metadata
884
+ api_key_param = metadata.get("api_key_param", "api_key")
885
+
886
+ # Get API key from user input or global variables
887
+ api_key = get_api_key_for_provider(user_id, provider, api_key)
888
+
889
+ # Validate API key (Ollama doesn't require one)
890
+ if not api_key and provider != "Ollama":
891
+ # Get the correct variable name from the provider variable mapping
892
+ provider_variable_map = get_model_provider_variable_mapping()
893
+ variable_name = provider_variable_map.get(provider, f"{provider.upper().replace(' ', '_')}_API_KEY")
894
+ msg = (
895
+ f"{provider} API key is required when using {provider} provider. "
896
+ f"Please provide it in the component or configure it globally as {variable_name}."
897
+ )
898
+ raise ValueError(msg)
899
+
900
+ # Get model class from metadata
901
+ model_class = get_model_classes().get(metadata.get("model_class"))
902
+ if model_class is None:
903
+ msg = f"No model class defined for {model_name}"
904
+ raise ValueError(msg)
905
+ model_name_param = metadata.get("model_name_param", "model")
906
+
907
+ # Check if this is a reasoning model that doesn't support temperature
908
+ reasoning_models = metadata.get("reasoning_models", [])
909
+ if model_name in reasoning_models:
910
+ temperature = None
911
+
912
+ # Build kwargs dynamically
913
+ kwargs = {
914
+ model_name_param: model_name,
915
+ "streaming": stream,
916
+ api_key_param: api_key,
917
+ }
918
+
919
+ if temperature is not None:
920
+ kwargs["temperature"] = temperature
921
+
922
+ # Add provider-specific parameters
923
+ if provider == "IBM WatsonX":
924
+ # For watsonx, url and project_id are required parameters
925
+ # Only add them if both are provided by the component
926
+ # If neither are provided, let ChatWatsonx handle it with its native error
927
+ # This allows components without WatsonX-specific fields to fail gracefully
928
+
929
+ url_param = metadata.get("url_param", "url")
930
+ project_id_param = metadata.get("project_id_param", "project_id")
931
+
932
+ has_url = watsonx_url is not None
933
+ has_project_id = watsonx_project_id is not None
934
+
935
+ if has_url and has_project_id:
936
+ # Both provided - add them to kwargs
937
+ kwargs[url_param] = watsonx_url
938
+ kwargs[project_id_param] = watsonx_project_id
939
+ elif has_url or has_project_id:
940
+ # Only one provided - this is a misconfiguration in the component
941
+ missing = "project ID" if has_url else "URL"
942
+ provided = "URL" if has_url else "project ID"
943
+ msg = (
944
+ f"IBM WatsonX requires both a URL and project ID. "
945
+ f"You provided a watsonx {provided} but no {missing}. "
946
+ f"Please add a 'watsonx {missing.title()}' field to your component or use the Language Model component "
947
+ f"which fully supports IBM WatsonX configuration."
948
+ )
949
+ raise ValueError(msg)
950
+ # else: neither provided - let ChatWatsonx handle it (will fail with its own error)
951
+ elif provider == "Ollama" and ollama_base_url:
952
+ # For Ollama, handle custom base_url
953
+ base_url_param = metadata.get("base_url_param", "base_url")
954
+ kwargs[base_url_param] = ollama_base_url
955
+
956
+ try:
957
+ return model_class(**kwargs)
958
+ except Exception as e:
959
+ # If instantiation fails and it's WatsonX, provide additional context
960
+ if provider == "IBM WatsonX" and ("url" in str(e).lower() or "project" in str(e).lower()):
961
+ msg = (
962
+ f"Failed to initialize IBM WatsonX model: {e}\n\n"
963
+ "IBM WatsonX requires additional configuration parameters (API endpoint URL and project ID). "
964
+ "This component may not support these parameters. "
965
+ "Consider using the 'Language Model' component instead, which fully supports IBM WatsonX."
966
+ )
967
+ raise ValueError(msg) from e
968
+ # Re-raise the original exception for other cases
969
+ raise
970
+
971
+
972
+ def update_model_options_in_build_config(
973
+ component: Any,
974
+ build_config: dict,
975
+ cache_key_prefix: str,
976
+ get_options_func: Callable,
977
+ field_name: str | None = None,
978
+ field_value: Any = None,
979
+ ) -> dict:
980
+ """Helper function to update build config with cached model options.
981
+
982
+ Uses instance-level caching to avoid expensive database calls on every field change.
983
+ Cache is refreshed when:
984
+ - api_key changes (may enable/disable providers)
985
+ - Initial load (field_name is None)
986
+ - Cache is empty or expired
987
+ - Model field is being refreshed (field_name == "model")
988
+
989
+ Args:
990
+ component: Component instance with cache, user_id, and log attributes
991
+ build_config: The build configuration dict to update
992
+ cache_key_prefix: Prefix for the cache key (e.g., "language_model_options" or "embedding_model_options")
993
+ get_options_func: Function to call to get model options (e.g., get_language_model_options)
994
+ field_name: The name of the field being changed, if any
995
+ field_value: The current value of the field being changed, if any
996
+
997
+ Returns:
998
+ Updated build_config dict with model options and providers set
999
+ """
1000
+ import time
1001
+
1002
+ # Cache key based on user_id
1003
+ cache_key = f"{cache_key_prefix}_{component.user_id}"
1004
+ cache_timestamp_key = f"{cache_key}_timestamp"
1005
+ cache_ttl = 30 # 30 seconds TTL to catch global variable changes faster
1006
+
1007
+ # Check if cache is expired
1008
+ cache_expired = False
1009
+ if cache_timestamp_key in component.cache:
1010
+ time_since_cache = time.time() - component.cache[cache_timestamp_key]
1011
+ cache_expired = time_since_cache > cache_ttl
1012
+
1013
+ # Check if we need to refresh
1014
+ should_refresh = (
1015
+ field_name == "api_key" # API key changed
1016
+ or field_name is None # Initial load
1017
+ or field_name == "model" # Model field refresh button clicked
1018
+ or cache_key not in component.cache # Cache miss
1019
+ or cache_expired # Cache expired
1020
+ )
1021
+
1022
+ if should_refresh:
1023
+ # Fetch options based on user's enabled models
1024
+ try:
1025
+ options = get_options_func(user_id=component.user_id)
1026
+ # Cache the results with timestamp
1027
+ component.cache[cache_key] = {"options": options}
1028
+ component.cache[cache_timestamp_key] = time.time()
1029
+ except KeyError as exc:
1030
+ # If we can't get user-specific options, fall back to empty
1031
+ component.log("Failed to fetch user-specific model options: %s", exc)
1032
+ component.cache[cache_key] = {"options": []}
1033
+ component.cache[cache_timestamp_key] = time.time()
1034
+
1035
+ # Use cached results
1036
+ cached = component.cache.get(cache_key, {"options": []})
1037
+ build_config["model"]["options"] = cached["options"]
1038
+
1039
+ # Set default value on initial load when field is empty
1040
+ # Fetch from user's default model setting in the database
1041
+ if not field_value or field_value == "":
1042
+ options = cached.get("options", [])
1043
+ if options:
1044
+ # Determine model type based on cache_key_prefix
1045
+ model_type = "embeddings" if cache_key_prefix == "embedding_model_options" else "language"
1046
+
1047
+ # Try to get user's default model from the variable service
1048
+ default_model_name = None
1049
+ default_model_provider = None
1050
+ try:
1051
+
1052
+ async def _get_default_model():
1053
+ async with session_scope() as session:
1054
+ variable_service = get_variable_service()
1055
+ if variable_service is None:
1056
+ return None, None
1057
+ from langflow.services.variable.service import DatabaseVariableService
1058
+
1059
+ if not isinstance(variable_service, DatabaseVariableService):
1060
+ return None, None
1061
+
1062
+ # Variable names match those in the API
1063
+ var_name = (
1064
+ "__default_embedding_model__"
1065
+ if model_type == "embeddings"
1066
+ else "__default_language_model__"
1067
+ )
1068
+
1069
+ try:
1070
+ var = await variable_service.get_variable_object(
1071
+ user_id=UUID(component.user_id)
1072
+ if isinstance(component.user_id, str)
1073
+ else component.user_id,
1074
+ name=var_name,
1075
+ session=session,
1076
+ )
1077
+ if var and var.value:
1078
+ import json
1079
+
1080
+ parsed_value = json.loads(var.value)
1081
+ if isinstance(parsed_value, dict):
1082
+ return parsed_value.get("model_name"), parsed_value.get("provider")
1083
+ except (ValueError, json.JSONDecodeError, TypeError):
1084
+ # Variable not found or invalid format
1085
+ logger.info("Variable not found or invalid format", exc_info=True)
1086
+ return None, None
1087
+
1088
+ default_model_name, default_model_provider = run_until_complete(_get_default_model())
1089
+ except Exception: # noqa: BLE001
1090
+ # If we can't get default model, continue without it
1091
+ logger.info("Failed to get default model, continue without it", exc_info=True)
1092
+
1093
+ # Find the default model in options
1094
+ default_model = None
1095
+ if default_model_name and default_model_provider:
1096
+ # Look for the user's preferred default model
1097
+ for opt in options:
1098
+ if opt.get("name") == default_model_name and opt.get("provider") == default_model_provider:
1099
+ default_model = opt
1100
+ break
1101
+
1102
+ # If user's default not found, fallback to first option
1103
+ if not default_model and options:
1104
+ default_model = options[0]
1105
+
1106
+ # Set the value
1107
+ if default_model:
1108
+ build_config["model"]["value"] = [default_model]
1109
+
1110
+ # Handle visibility logic:
1111
+ # - Show handle ONLY when field_value is "connect_other_models"
1112
+ # - Hide handle in all other cases (default, model selection, etc.)
1113
+ if field_value == "connect_other_models":
1114
+ # User explicitly selected "Connect other models", show the handle
1115
+ if cache_key_prefix == "embedding_model_options":
1116
+ build_config["model"]["input_types"] = ["Embeddings"]
1117
+ else:
1118
+ build_config["model"]["input_types"] = ["LanguageModel"]
1119
+ else:
1120
+ # Default case or model selection: hide the handle
1121
+ build_config["model"]["input_types"] = []
1122
+
1123
+ return build_config