Skip to content

fix: multi embedding available models issue and improved logging#10846

Merged
edwinjosechittilappilly merged 24 commits into
mainfrom
fix-opensearch-search
Dec 3, 2025
Merged

fix: multi embedding available models issue and improved logging#10846
edwinjosechittilappilly merged 24 commits into
mainfrom
fix-opensearch-search

Conversation

@edwinjosechittilappilly
Copy link
Copy Markdown
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly commented Dec 2, 2025

This pull request makes a minor update to the Nvidia Remix.json starter project by changing the code_hash value in the metadata section.

  • Updated the code_hash in the metadata of src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json to reflect the latest code version.

Summary by CodeRabbit

  • New Features

    • Added fail-safe mode to embedding model component for graceful error handling
  • Improvements

    • Enhanced error handling and fallback mechanisms for model initialization across providers
    • Improved model detection resilience with better diagnostics when models are unavailable
    • More informative error messages when search operations encounter issues

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 2, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The PR introduces fail-safe error handling to the EmbeddingModelComponent through a new fail_safe_mode boolean input that gracefully handles provider initialization failures across OpenAI, Ollama, and IBM watsonx.ai. Additionally, comprehensive logging instrumentation is added to the OpenSearch multimodal component for improved observability during search and ingestion operations.

Changes

Cohort / File(s) Change Summary
EmbeddingModelComponent Configuration
src/backend/base/langflow/initial_setup/starter_projects/Nvidia\ Remix.json
Added fail_safe_mode BoolInput to enable graceful error handling; logs and returns None on provider initialization errors instead of raising. Introduced fetch_ollama_models() helper method. Updated update_build_config to handle fail_safe_mode toggle effects on field visibility and provider transitions.
OpenSearch Multimodal Logging Enhancement
src/lfx/src/lfx/components/elastic/opensearch_multimodal.py
Added comprehensive logging throughout ingestion and search workflows: model detection resilience with fallback logging, runtime traces for embedding availability and model selection, KNN query debugging for dimension mismatches, enhanced error messages with available models, and execution context logs during multi-model hybrid search.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • The fail_safe_mode logic in update_build_config requires careful review to ensure field visibility and provider transitions are correctly handled across all three providers.
  • Provider-specific error handling paths (OpenAI, Ollama, IBM watsonx.ai) need verification that fail_safe_mode state is properly propagated.
  • Verify that the extensive logging additions in opensearch_multimodal.py don't introduce performance overhead or redundant entries in existing traces.

Possibly related PRs

Suggested labels

enhancement, size:S, lgtm

Suggested reviewers

  • phact
  • ogabrielluiz

Pre-merge checks and finishing touches

❌ Failed checks (1 error, 2 warnings)
Check name Status Explanation Resolution
Test Coverage For New Implementations ❌ Error Test coverage is incomplete for the new fail_safe_mode parameter, error handling logic, and logging changes introduced in the PR, missing regression tests for identified bugs. Add comprehensive tests for fail_safe_mode behavior across providers, regression tests for Ollama base URL and IBM watsonx.ai bugs, and logging instrumentation validation.
Test Quality And Coverage ⚠️ Warning Pull request introduces substantial new functionality but lacks adequate test coverage for enhanced logging, error handling, fail_safe_mode, and new async methods. Add comprehensive tests for fail_safe_mode, fetch_ollama_models async method, model-detection resilience, search instrumentation, and KNN debugging across all providers.
Title check ⚠️ Warning The PR title describes improved logging and multi-embedding model handling, but the actual changes span two distinct files with different focuses: metadata updates in a starter project JSON file and logging enhancements in an OpenSearch component. The title is partially related but does not capture the full scope or the primary change (metadata code_hash update). Revise the title to reflect both changes, such as 'chore: update Nvidia Remix metadata and improve opensearch logging' or split into separate PRs if these are unrelated changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Test File Naming And Structure ✅ Passed The PR modifies source files without adding new test files, but existing well-organized pytest tests provide comprehensive coverage of modified components.
Excessive Mock Usage Warning ✅ Passed Test file uses real test doubles (MockEmbeddings class) instead of excessive mocks, with only one MagicMock for external OpenSearch client and minimal @patch decorators, demonstrating appropriate mock usage discipline.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 2, 2025

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 15%
15.44% (4246/27500) 8.61% (1811/21013) 9.69% (587/6057)

Unit Test Results

Tests Skipped Failures Errors Time
1671 0 💤 0 ❌ 0 🔥 21.15s ⏱️

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 2, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 32.51%. Comparing base (3010a42) to head (23569ab).
⚠️ Report is 2 commits behind head on main.

❌ Your project status has failed because the head coverage (39.99%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main   #10846   +/-   ##
=======================================
  Coverage   32.51%   32.51%           
=======================================
  Files        1370     1370           
  Lines       63494    63494           
  Branches     9391     9391           
=======================================
+ Hits        20643    20647    +4     
+ Misses      41812    41808    -4     
  Partials     1039     1039           
Flag Coverage Δ
backend 51.45% <ø> (+0.02%) ⬆️
frontend 14.29% <ø> (ø)
lfx 39.99% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/lfx/src/lfx/components/elastic/opensearch_multimodal.py (3)

52-52: Consider reducing log verbosity in utility function.

This INFO-level log in get_embedding_field_name will fire on every call, which could be frequent during search operations with multiple models. Since the function returns the field name and it's already logged in context where it matters (lines 817, 965), this log entry adds redundancy.

Consider changing to DEBUG level or removing this log:

-    logger.info(f"chunk_embedding_{normalize_model_name(model_name)}")
+    logger.debug(f"chunk_embedding_{normalize_model_name(model_name)}")

Or simply remove it since the field name is logged in more meaningful contexts elsewhere.


1324-1379: Consider consolidating verbose model matching logs.

The detailed model matching instrumentation is excellent for debugging, but the high number of self.log calls (visible to end users) could be overwhelming when processing many models. Lines 1324-1378 generate at least 2-3 logs per operation plus additional logs per model.

Consider consolidating related logs into single entries:

-        self.log(f"[SEARCH] Models detected in index: {available_models}")
-        self.log(f"[SEARCH] Available embedding identifiers: {list(embedding_by_model.keys())}")
+        self.log(f"[SEARCH] Models detected: {available_models}, Available identifiers: {list(embedding_by_model.keys())[:10]}{'...' if len(embedding_by_model) > 10 else ''}")

Also consider truncating the identifier list in line 1367 if it becomes very long:

-                        f"This model will be skipped. Available identifiers: {list(embedding_by_model.keys())}"
+                        f"This model will be skipped. Available identifiers: {list(embedding_by_model.keys())[:10]}{'...' if len(embedding_by_model) > 10 else ''}"

1534-1540: Consider consolidating execution logs.

Lines 1534-1540 create 4 separate log entries in quick succession. While the information is valuable, this could be overwhelming in production environments with high query volume.

Consider consolidating into 1-2 log entries:

-        self.log(f"[EXEC] Executing search with {len(knn_queries_with_candidates)} KNN queries, limit={limit}")
-        self.log(f"[EXEC] Embedding models used: {list(query_embeddings.keys())}")
-        self.log(f"[EXEC] KNN fields being queried: {embedding_fields}")
+        self.log(
+            f"[EXEC] Search: {len(knn_queries_with_candidates)} KNN queries, "
+            f"models={list(query_embeddings.keys())}, fields={embedding_fields}, limit={limit}"
+        )
src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json (1)

2038-2058: Fail-safe mode input: good addition; align required flags.

The BoolInput is clear. To match runtime checks, also toggle project_id.required for IBM in update_build_config when fail_safe_mode flips (on=false → required; on=true → optional), same as you do for api_key.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8b979d and db4fc82.

📒 Files selected for processing (2)
  • src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json (3 hunks)
  • src/lfx/src/lfx/components/elastic/opensearch_multimodal.py (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/lfx/src/lfx/components/elastic/opensearch_multimodal.py (1)
src/lfx/src/lfx/custom/custom_component/component.py (1)
  • log (1523-1540)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (65)
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 6/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 48/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 38/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 45/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 41/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 46/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 40/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 37/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 23/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 50/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 44/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 42/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 47/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 34/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 36/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 35/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 27/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 31/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 39/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 49/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 43/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 29/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 33/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 28/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 32/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 25/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 30/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 21/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 26/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 22/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 20/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 24/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 18/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 19/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 17/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 10/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 11/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 15/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 16/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 14/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 12/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 4/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 13/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 9/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 3/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 8/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 7/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 5/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 2/50
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 1/50
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
  • GitHub Check: Test Docker Images / Test docker images
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
  • GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
  • GitHub Check: Run Backend Tests / LFX Tests - Python 3.10
  • GitHub Check: Lint Backend / Run Mypy (3.11)
  • GitHub Check: Lint Backend / Run Mypy (3.12)
  • GitHub Check: Test Starter Templates
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Run Ruff Check and Format
  • GitHub Check: Update Component Index
  • GitHub Check: test-starter-projects
🔇 Additional comments (8)
src/lfx/src/lfx/components/elastic/opensearch_multimodal.py (7)

649-650: LGTM! Improved log clarity.

Removing the emoji and using consistent debug-level logging improves log parsing and maintains appropriate verbosity levels for mode detection.


684-684: LGTM! Consistent logging format.

These changes standardize log messages by removing emojis and using clear, parseable text. This improves consistency across the component and makes logs easier to process programmatically.

Also applies to: 815-815, 983-984


1080-1107: Excellent defensive logging in model detection.

The enhanced logging in _detect_available_models provides valuable diagnostic information:

  • Query logging helps debug aggregation issues
  • Warning with total document count helps identify whether the index is empty or documents lack the embedding_model field
  • Fallback behavior is now transparent to operators

This will significantly improve troubleshooting of model detection issues.


1380-1387: LGTM! Enhanced error context.

The improved error message provides clear, actionable information when embedding generation fails, including both the models in the index and the available embedding identifiers. This will significantly reduce debugging time.


1416-1416: Excellent validation logging.

These logs surface critical validation failures that would otherwise be opaque to users:

  • Non-knn_vector field detection prevents runtime errors
  • Dimension mismatch detection prevents cryptic OpenSearch errors
  • Empty query list provides clear context for why search returned no results

This defensive logging will greatly improve the user experience.

Also applies to: 1427-1429, 1466-1470


1631-1631: Verify search query logging doesn't expose sensitive data.

Logging the search query at the start of search_documents is helpful for debugging, but ensure that search queries in your use case don't contain sensitive user information (PII, credentials, etc.).

If search queries may contain sensitive data, consider:

  1. Adding a flag to enable/disable query logging
  2. Truncating long queries
  3. Redacting patterns that look like sensitive data (emails, SSNs, etc.)

Otherwise, this is a valuable addition for tracing search operations.


1594-1603: LGTM! Valuable empty result debugging.

The debug information logged when no results are found (lines 1596-1603) provides excellent context for troubleshooting, including models in index, matched models, KNN fields, and filter counts. This will help users understand why their search returned empty results.

src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json (1)

1856-1856: LGTM: metadata code hash bump.

The code_hash update looks correct for this starter project snapshot.

Comment on lines +2018 to 2019
"value": "from typing import Any\n\nimport requests\nfrom ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames\nfrom langchain_openai import OpenAIEmbeddings\n\nfrom lfx.base.embeddings.embeddings_class import EmbeddingsWithModels\nfrom lfx.base.embeddings.model import LCEmbeddingsModel\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES\nfrom lfx.base.models.watsonx_constants import (\n IBM_WATSONX_URLS,\n WATSONX_EMBEDDING_MODEL_NAMES,\n)\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageTextInput,\n SecretStrInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"embedding\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass EmbeddingModelComponent(LCEmbeddingsModel):\n display_name = \"Embedding Model\"\n description = \"Generate embeddings using a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-embedding-models\"\n icon = \"binary\"\n name = \"EmbeddingModel\"\n category = \"models\"\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Ollama\", \"IBM watsonx.ai\"],\n value=\"OpenAI\",\n info=\"Select the embedding model provider\",\n real_time_refresh=True,\n options_metadata=[{\"icon\": \"OpenAI\"}, {\"icon\": \"Ollama\"}, {\"icon\": \"WatsonxAI\"}],\n ),\n MessageTextInput(\n name=\"api_base\",\n display_name=\"API Base URL\",\n info=\"Base URL for the API. Leave empty for default.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"model\",\n display_name=\"Model Name\",\n options=OPENAI_EMBEDDING_MODEL_NAMES,\n value=OPENAI_EMBEDDING_MODEL_NAMES[0],\n info=\"Select the embedding model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=True,\n show=True,\n real_time_refresh=True,\n ),\n # Watson-specific inputs\n MessageTextInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"IBM watsonx.ai Project ID (required for IBM watsonx.ai)\",\n show=False,\n ),\n IntInput(\n name=\"dimensions\",\n display_name=\"Dimensions\",\n info=\"The number of dimensions the resulting output embeddings should have. \"\n \"Only supported by certain models.\",\n advanced=True,\n ),\n IntInput(name=\"chunk_size\", display_name=\"Chunk Size\", advanced=True, value=1000),\n FloatInput(name=\"request_timeout\", display_name=\"Request Timeout\", advanced=True),\n IntInput(name=\"max_retries\", display_name=\"Max Retries\", advanced=True, value=3),\n BoolInput(name=\"show_progress_bar\", display_name=\"Show Progress Bar\", advanced=True),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n IntInput(\n name=\"truncate_input_tokens\",\n display_name=\"Truncate Input Tokens\",\n advanced=True,\n value=200,\n show=False,\n ),\n BoolInput(\n name=\"input_text\",\n display_name=\"Include the original text in the output\",\n value=True,\n advanced=True,\n show=False,\n ),\n BoolInput(\n name=\"fail_safe_mode\",\n display_name=\"Fail-Safe Mode\",\n value=False,\n advanced=True,\n info=\"When enabled, errors will be logged instead of raising exceptions. \"\n \"The component will return None on error.\",\n real_time_refresh=True,\n ),\n ]\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\n \"version\": \"2024-09-16\",\n \"filters\": \"function_embedding,!lifecycle_withdrawn:and\",\n }\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching models\")\n return WATSONX_EMBEDDING_MODEL_NAMES\n\n async def fetch_ollama_models(self) -> list[str]:\n try:\n return await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching models\")\n return []\n\n async def build_embeddings(self) -> Embeddings:\n provider = self.provider\n model = self.model\n api_key = self.api_key\n api_base = self.api_base\n base_url_ibm_watsonx = self.base_url_ibm_watsonx\n ollama_base_url = self.ollama_base_url\n dimensions = self.dimensions\n chunk_size = self.chunk_size\n request_timeout = self.request_timeout\n max_retries = self.max_retries\n show_progress_bar = self.show_progress_bar\n model_kwargs = self.model_kwargs or {}\n\n if provider == \"OpenAI\":\n if not api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n try:\n # Create the primary embedding instance\n embeddings_instance = OpenAIEmbeddings(\n model=model,\n dimensions=dimensions or None,\n base_url=api_base or None,\n api_key=api_key,\n chunk_size=chunk_size,\n max_retries=max_retries,\n timeout=request_timeout or None,\n show_progress_bar=show_progress_bar,\n model_kwargs=model_kwargs,\n )\n\n # Create dedicated instances for each available model\n available_models_dict = {}\n for model_name in OPENAI_EMBEDDING_MODEL_NAMES:\n available_models_dict[model_name] = OpenAIEmbeddings(\n model=model_name,\n dimensions=dimensions or None, # Use same dimensions config for all\n base_url=api_base or None,\n api_key=api_key,\n chunk_size=chunk_size,\n max_retries=max_retries,\n timeout=request_timeout or None,\n show_progress_bar=show_progress_bar,\n model_kwargs=model_kwargs,\n )\n\n return EmbeddingsWithModels(\n embeddings=embeddings_instance,\n available_models=available_models_dict,\n )\n except Exception as e:\n msg = f\"Failed to initialize OpenAI embeddings: {e}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise\n\n if provider == \"Ollama\":\n try:\n from langchain_ollama import OllamaEmbeddings\n except ImportError:\n try:\n from langchain_community.embeddings import OllamaEmbeddings\n except ImportError:\n msg = \"Please install langchain-ollama: pip install langchain-ollama\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ImportError(msg) from None\n\n try:\n transformed_base_url = transform_localhost_url(ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n final_base_url = transformed_base_url or \"http://localhost:11434\"\n\n # Create the primary embedding instance\n embeddings_instance = OllamaEmbeddings(\n model=model,\n base_url=final_base_url,\n **model_kwargs,\n )\n\n # Fetch available Ollama models\n available_model_names = await self.fetch_ollama_models()\n\n # Create dedicated instances for each available model\n available_models_dict = {}\n for model_name in available_model_names:\n available_models_dict[model_name] = OllamaEmbeddings(\n model=model_name,\n base_url=final_base_url,\n **model_kwargs,\n )\n\n return EmbeddingsWithModels(\n embeddings=embeddings_instance,\n available_models=available_models_dict,\n )\n except Exception as e:\n msg = f\"Failed to initialize Ollama embeddings: {e}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise\n\n if provider == \"IBM watsonx.ai\":\n try:\n from langchain_ibm import WatsonxEmbeddings\n except ImportError:\n msg = \"Please install langchain-ibm: pip install langchain-ibm\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ImportError(msg) from None\n\n if not api_key:\n msg = \"IBM watsonx.ai API key is required when using IBM watsonx.ai provider\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n project_id = self.project_id\n\n if not project_id:\n msg = \"Project ID is required for IBM watsonx.ai provider\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n try:\n from ibm_watsonx_ai import APIClient, Credentials\n\n final_url = base_url_ibm_watsonx or \"https://us-south.ml.cloud.ibm.com\"\n\n credentials = Credentials(\n api_key=self.api_key,\n url=final_url,\n )\n\n api_client = APIClient(credentials)\n\n params = {\n EmbedTextParamsMetaNames.TRUNCATE_INPUT_TOKENS: self.truncate_input_tokens,\n EmbedTextParamsMetaNames.RETURN_OPTIONS: {\"input_text\": self.input_text},\n }\n\n # Create the primary embedding instance\n embeddings_instance = WatsonxEmbeddings(\n model_id=model,\n params=params,\n watsonx_client=api_client,\n project_id=project_id,\n )\n\n # Fetch available IBM watsonx.ai models\n available_model_names = self.fetch_ibm_models(final_url)\n\n # Create dedicated instances for each available model\n available_models_dict = {}\n for model_name in available_model_names:\n available_models_dict[model_name] = WatsonxEmbeddings(\n model_id=model_name,\n params=params,\n watsonx_client=api_client,\n project_id=project_id,\n )\n\n return EmbeddingsWithModels(\n embeddings=embeddings_instance,\n available_models=available_models_dict,\n )\n except Exception as e:\n msg = f\"Failed to authenticate with IBM watsonx.ai: {e}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise\n\n msg = f\"Unknown provider: {provider}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n # Handle fail_safe_mode changes first - set all required fields to False if enabled\n if field_name == \"fail_safe_mode\":\n if field_value: # If fail_safe_mode is enabled\n build_config[\"api_key\"][\"required\"] = False\n elif hasattr(self, \"provider\"):\n # If fail_safe_mode is disabled, restore required flags based on provider\n if self.provider in [\"OpenAI\", \"IBM watsonx.ai\"]:\n build_config[\"api_key\"][\"required\"] = True\n else: # Ollama\n build_config[\"api_key\"][\"required\"] = False\n\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model\"][\"options\"] = OPENAI_EMBEDDING_MODEL_NAMES\n build_config[\"model\"][\"value\"] = OPENAI_EMBEDDING_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n # Only set required=True if fail_safe_mode is not enabled\n build_config[\"api_key\"][\"required\"] = not (hasattr(self, \"fail_safe_mode\") and self.fail_safe_mode)\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"api_base\"][\"display_name\"] = \"OpenAI API Base URL\"\n build_config[\"api_base\"][\"advanced\"] = True\n build_config[\"api_base\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"truncate_input_tokens\"][\"show\"] = False\n build_config[\"input_text\"][\"show\"] = False\n elif field_value == \"Ollama\":\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await self.fetch_ollama_models()\n build_config[\"model\"][\"options\"] = models\n build_config[\"model\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n build_config[\"model\"][\"options\"] = []\n build_config[\"model\"][\"value\"] = \"\"\n else:\n build_config[\"model\"][\"options\"] = []\n build_config[\"model\"][\"value\"] = \"\"\n build_config[\"truncate_input_tokens\"][\"show\"] = False\n build_config[\"input_text\"][\"show\"] = False\n build_config[\"api_key\"][\"display_name\"] = \"API Key (Optional)\"\n build_config[\"api_key\"][\"required\"] = False\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"api_base\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model\"][\"options\"] = self.fetch_ibm_models(base_url=self.base_url_ibm_watsonx)\n build_config[\"model\"][\"value\"] = self.fetch_ibm_models(base_url=self.base_url_ibm_watsonx)[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM watsonx.ai API Key\"\n # Only set required=True if fail_safe_mode is not enabled\n build_config[\"api_key\"][\"required\"] = not (hasattr(self, \"fail_safe_mode\") and self.fail_safe_mode)\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"api_base\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"truncate_input_tokens\"][\"show\"] = True\n build_config[\"input_text\"][\"show\"] = True\n elif field_name == \"base_url_ibm_watsonx\":\n build_config[\"model\"][\"options\"] = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model\"][\"value\"] = self.fetch_ibm_models(base_url=field_value)[0]\n elif field_name == \"ollama_base_url\":\n # # Refresh Ollama models when base URL changes\n # if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n # Use field_value if provided, otherwise fall back to instance attribute\n ollama_url = self.ollama_base_url\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await self.fetch_ollama_models()\n build_config[\"model\"][\"options\"] = models\n build_config[\"model\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama embedding models.\")\n build_config[\"model\"][\"options\"] = []\n build_config[\"model\"][\"value\"] = \"\"\n\n elif field_name == \"model\" and self.provider == \"Ollama\":\n ollama_url = self.ollama_base_url\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await self.fetch_ollama_models()\n build_config[\"model\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama embedding models.\")\n build_config[\"model\"][\"options\"] = []\n\n return build_config\n"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the file exists and check its structure
head -20 "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json"

Repository: langflow-ai/langflow

Length of output: 537


🏁 Script executed:

# Check file size to understand what we're working with
wc -l "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json"

Repository: langflow-ai/langflow

Length of output: 143


🏁 Script executed:

# Extract lines around 2018-2019 to see the context
sed -n '2000,2100p' "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json" | head -100

Repository: langflow-ai/langflow

Length of output: 24862


🏁 Script executed:

# Extract the Python code value from the JSON by parsing it
python3 << 'EOF'
import json
import re

# Read the JSON file
with open("src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json", "r") as f:
    data = json.load(f)

# Navigate to the code value - it should be in nodes
for node in data.get("data", {}).get("nodes", []):
    if "data" in node and "code" in node["data"]:
        code_value = node["data"]["code"].get("value", "")
        if "update_build_config" in code_value:
            # Find the ollama_base_url handler section
            lines = code_value.split('\n')
            for i, line in enumerate(lines):
                if "elif field_name == \"ollama_base_url\"" in line:
                    # Print 20 lines starting from this handler
                    print(f"\n=== OLLAMA_BASE_URL HANDLER (lines {i}-{i+20}) ===")
                    for j in range(i, min(i+25, len(lines))):
                        print(f"{j}: {lines[j]}")
                
                if "elif field_value == \"IBM watsonx.ai\"" in line:
                    # Print IBM handler
                    print(f"\n=== IBM WATSONX HANDLER (lines {i}-{i+15}) ===")
                    for j in range(i, min(i+20, len(lines))):
                        print(f"{j}: {lines[j]}")
            
            # Check return type annotation of build_embeddings
            for i, line in enumerate(lines):
                if "async def build_embeddings" in line:
                    print(f"\n=== BUILD_EMBEDDINGS RETURN TYPE (lines {i}-{i+2}) ===")
                    for j in range(i, min(i+2, len(lines))):
                        print(f"{j}: {lines[j]}")
                    break
EOF

Repository: langflow-ai/langflow

Length of output: 150


🏁 Script executed:

# Use sed to extract the relevant sections from around line 2018
# Focus on the update_build_config method
sed -n '4700,4850p' "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json"

Repository: langflow-ai/langflow

Length of output: 8029


🏁 Script executed:

# Find lines containing ollama_base_url handler
rg -n "elif field_name == \"ollama_base_url\"" "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json"

Repository: langflow-ai/langflow

Length of output: 46


🏁 Script executed:

# Find where ollama_base_url appears in the file
rg -n "ollama_base_url" "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json" | head -20

Repository: langflow-ai/langflow

Length of output: 21301


🏁 Script executed:

# Extract just the relevant sections to verify each concern
cat > /tmp/verify.py << 'EOF'
import json

with open("src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json", "r") as f:
    data = json.load(f)

# Find the code node
for node in data.get("data", {}).get("nodes", []):
    if "data" in node and "code" in node["data"]:
        code = node["data"]["code"].get("value", "")
        if "fetch_ollama_models" in code:
            lines = code.split('\n')
            
            # Check 1: fetch_ollama_models implementation
            print("=== CONCERN 1: fetch_ollama_models uses self.ollama_base_url ===")
            for i, line in enumerate(lines):
                if "async def fetch_ollama_models" in line:
                    for j in range(i, min(i+10, len(lines))):
                        print(f"{j}: {lines[j]}")
                    break
            
            # Check 2: Return type of build_embeddings
            print("\n=== CONCERN 2: build_embeddings return type ===")
            for i, line in enumerate(lines):
                if "async def build_embeddings" in line:
                    print(f"{i}: {lines[i]}")
                    break
            
            # Check 3: project_id input definition
            print("\n=== CONCERN 3: project_id input definition ===")
            for i, line in enumerate(lines):
                if 'name="project_id"' in line:
                    for j in range(max(0, i-2), min(i+8, len(lines))):
                        print(f"{j}: {lines[j]}")
                    break
            
            # Check 4: IBM models fetch calls
            print("\n=== CONCERN 4: IBM models double fetch ===")
            for i, line in enumerate(lines):
                if 'elif field_value == "IBM watsonx.ai"' in line:
                    for j in range(i, min(i+8, len(lines))):
                        print(f"{j}: {lines[j]}")
                    break
            
            break
EOF
python3 /tmp/verify.py

Repository: langflow-ai/langflow

Length of output: 46


🏁 Script executed:

# Let me extract the specific problematic code sections directly
# The code is on line 2018 as a JSON value, let me search within it more carefully

# First, let's look for the exact patterns in the problematic sections
sed -n '2018,2019p' "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json" | \
  grep -o "async def fetch_ollama_models.*\}" | head -1 | sed 's/\\n/\n/g' | head -20

Repository: langflow-ai/langflow

Length of output: 848


🏁 Script executed:

# Extract IBM watsonx.ai handler to verify double fetch issue
sed -n '2018,2019p' "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json" | \
  grep -o 'elif field_value == "IBM watsonx.ai":.*elif field_name == "base_url_ibm_watsonx"' | \
  sed 's/\\n/\n/g' | head -30

Repository: langflow-ai/langflow

Length of output: 46


🏁 Script executed:

# Extract the complete ollama_base_url handler section
sed -n '2018,2019p' "src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json" | \
  python3 << 'PYSCRIPT'
import sys
content = sys.stdin.read()
# Find ollama_base_url handler
start = content.find('elif field_name == "ollama_base_url":')
if start != -1:
    # Extract next 500 chars to see the handler
    section = content[start:start+800]
    # Unescape the newlines
    section = section.replace('\\n', '\n')
    print("=== OLLAMA_BASE_URL HANDLER ===")
    print(section)
PYSCRIPT

Repository: langflow-ai/langflow

Length of output: 46


Ollama base URL handler ignores field_value—model refresh fails when URL is changed in UI.

In update_build_config, the ollama_base_url branch reads self.ollama_base_url instead of field_value, causing model refresh to skip the newly entered URL unless the instance attribute is already updated. Pass field_value to the model fetch:

         elif field_name == "ollama_base_url":
-            ollama_url = self.ollama_base_url
+            ollama_url = field_value or self.ollama_base_url
             if await is_valid_ollama_url(url=ollama_url):

Additional issues:

  • Double IBM network calls: self.fetch_ibm_models() is called twice when provider changes to IBM watsonx.ai (once for options, once for value). Cache the result.
  • Project ID should be conditionally required: Set build_config["project_id"]["required"] = not (hasattr(self, "fail_safe_mode") and self.fail_safe_mode) to match the runtime validation in build_embeddings.
  • Inaccurate error message: "Failed to authenticate with IBM watsonx.ai" should be "Failed to initialize IBM watsonx.ai embeddings" since errors can occur during client initialization, not just authentication.
  • Return type annotation mismatch: build_embeddings is annotated as -> Embeddings but returns None when fail_safe_mode is enabled. Change to -> Embeddings | None and ensure downstream components check for None.
🤖 Prompt for AI Agents
In src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json
around lines 2018-2019: the ollama_base_url branch of update_build_config uses
self.ollama_base_url instead of the incoming field_value (so UI changes don't
refresh models); change to use field_value (or prefer field_value when present)
when validating and fetching models. Also, when provider == "IBM watsonx.ai"
avoid calling self.fetch_ibm_models() twice by calling it once, storing the
result and using it for both model options and default value; set
build_config["project_id"]["required"] = not (hasattr(self, "fail_safe_mode")
and self.fail_safe_mode) to match runtime validation; change the IBM exception
message from "Failed to authenticate with IBM watsonx.ai" to "Failed to
initialize IBM watsonx.ai embeddings"; and update the build_embeddings signature
return annotation from -> Embeddings to -> Embeddings | None (and ensure callers
handle None).

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Dec 2, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Dec 2, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Dec 2, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Dec 2, 2025
Copilot AI review requested due to automatic review settings December 3, 2025 05:12
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Dec 3, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Dec 3, 2025
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances error handling and diagnostic logging for the OpenSearch multimodal component's embedding model management. The changes focus on improving model detection resilience and providing better visibility into search operations through comprehensive logging.

Key Changes:

  • Enhanced model detection with detailed diagnostics when models are unavailable
  • Added extensive debug logging throughout the search pipeline to track model matching and embedding generation
  • Improved error messages with contextual information about available models and identifiers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

model_name = getattr(emb_obj, "model_name", None)
dimensions = getattr(emb_obj, "dimensions", None)
available_models = getattr(emb_obj, "available_models", None)
available_models_attr = getattr(emb_obj, "available_models", None)
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The variable name 'available_models_attr' is verbose. Consider renaming to 'available_models_dict' to better convey that it's specifically the dictionary attribute, or simply keep 'available_models' if there's no naming conflict in this scope.

Copilot uses AI. Check for mistakes.
@edwinjosechittilappilly edwinjosechittilappilly added this pull request to the merge queue Dec 3, 2025
Merged via the queue into main with commit 7f07356 Dec 3, 2025
52 of 53 checks passed
@edwinjosechittilappilly edwinjosechittilappilly deleted the fix-opensearch-search branch December 3, 2025 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants