Skip to content

fix: Set Model after Provider is deleted#1935

Open
mfortman11 wants to merge 7 commits into
mainfrom
model-sel-after-provider-delete
Open

fix: Set Model after Provider is deleted#1935
mfortman11 wants to merge 7 commits into
mainfrom
model-sel-after-provider-delete

Conversation

@mfortman11

@mfortman11 mfortman11 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

How to replicate and test

  1. Add a second provider and select models from that providers
  2. Delete that provider
  3. Previously: The model input was empty
  4. Now: A default model is selected

Summary by CodeRabbit

Release Notes

  • New Features

    • Automatic fallback selection for LLM and embedding models using provider-appropriate defaults when models are unset
  • Updates

    • Default LLM model updated from “gpt-4o-mini” to “gpt-5.4-mini”
  • Bug Fixes

    • Improved behavior when removing or switching providers: models now reset to appropriate defaults instead of being cleared incorrectly
  • Tests

    • Added unit tests covering provider removal and default model selection for both LLMs and embeddings

@github-actions github-actions Bot added frontend 🟨 Issues related to the UI/UX backend 🔷 Issues related to backend services (OpenSearch, Langflow, APIs) tests community labels Jun 22, 2026
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 958ecfb9-c54b-4529-b9f8-d0c718334cb9

📥 Commits

Reviewing files that changed from the base of the PR and between 64903ff and 97f51c9.

📒 Files selected for processing (4)
  • frontend/app/settings/_components/agent-settings-section.tsx
  • frontend/app/settings/_components/ingest-settings-section.tsx
  • src/api/settings/helpers.py
  • tests/unit/test_settings_provider_removal_defaults.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/app/settings/_components/agent-settings-section.tsx
  • src/api/settings/helpers.py
  • tests/unit/test_settings_provider_removal_defaults.py
  • frontend/app/settings/_components/ingest-settings-section.tsx

Walkthrough

Backend helper functions _default_llm_model and _default_embedding_model are added to map provider names to static default model strings, with _first_configured_embedding_provider changed to return empty string instead of defaulting to "openai". These helpers replace empty-string clears in all four provider-removal branches (Ollama, OpenAI, Anthropic, WatsonX) in the settings endpoint. On the frontend, memoized model lists and a new useEffect in each settings component auto-select a fallback model when none is set. The default LLM constant is updated to "gpt-5.4-mini", and a new unit test module validates the helpers and removal fallback logic.

Changes

Default Model Fallback on Provider Removal

Layer / File(s) Summary
Default model helper functions
src/api/settings/helpers.py
Modifies _first_configured_embedding_provider to return "" instead of defaulting to "openai". Adds _default_llm_model(provider) and _default_embedding_model(provider) that return static default model names for openai/anthropic (LLM) and openai (embedding), returning "" for all other providers.
Provider-removal fallback in settings endpoint
src/api/settings/endpoints.py
Imports the new helpers and replaces empty-string assignments with _default_llm_model(fb) / _default_embedding_model(fb) in the Ollama, OpenAI, Anthropic, and WatsonX removal branches.
Frontend auto-select for missing models
frontend/lib/constants.ts, frontend/app/settings/_components/agent-settings-section.tsx, frontend/app/settings/_components/ingest-settings-section.tsx
Updates DEFAULT_AGENT_SETTINGS.llm_model to "gpt-5.4-mini". Memoizes grouped model lists and adds useEffect hooks in both settings components to auto-select and persist a fallback model when the current model setting is absent, preferring options marked default or falling back to the first available option.
Unit tests for helpers and removal fallback
tests/unit/test_settings_provider_removal_defaults.py
New test module with _make_config helper and tests covering _default_llm_model, _default_embedding_model, _first_configured_llm_provider, _first_configured_embedding_provider, and LLM/embedding provider removal simulations including empty-model and no-change cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • langflow-ai/openrag#1600: Calls provider_health_cache.invalidate() after settings/provider mutations in src/api/settings/endpoints.py, which is the same file being modified for provider-removal fallback logic in this PR.

Suggested reviewers

  • lucaseduoli
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: Set Model after Provider is deleted' directly describes the main objective of the PR, which is to ensure a default model is automatically selected after provider removal.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch model-sel-after-provider-delete

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 github-actions Bot added the bug 🔴 Something isn't working. label Jun 22, 2026
@github-actions github-actions Bot added bug 🔴 Something isn't working. and removed bug 🔴 Something isn't working. labels Jun 22, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/unit/test_settings_provider_removal_defaults.py (1)

250-255: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a regression test for “no embedding-capable fallback” removal.

This simulator only validates happy-path fallback. Add a case where only Anthropic remains configured and embedding provider is removed, then assert the flow rejects fallback (or raises) instead of silently selecting an unconfigured provider.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_settings_provider_removal_defaults.py` around lines 250 -
255, The `_simulate_embedding_removal` method in the test currently only
validates the happy-path scenario where a valid fallback embedding provider
exists. Add a new test case that configures only Anthropic as the embedding
provider (which is not embedding-capable), then calls
`_simulate_embedding_removal` to simulate removing that provider, and assert
that the method either raises an exception or explicitly rejects the fallback
instead of silently accepting an unconfigured provider. This regression test
ensures the code does not silently select invalid fallback providers when no
embedding-capable alternatives remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/app/settings/_components/agent-settings-section.tsx`:
- Around line 136-144: The useEffect that handles auto-selecting a default LLM
model is re-triggering multiple times because allLlmOptions is included in the
dependency array but is recreated on every render (since groupedLlmModels isn't
memoized). When model data loads incrementally, allLlmOptions changes and the
effect re-runs, and since the initial mutation is still pending and
settings.agent?.llm_model is still undefined, handleModelChange fires again
causing duplicate mutations. Fix this by either memoizing groupedLlmModels using
useMemo so it only changes when its actual dependencies change, or by using a
useRef to track whether the auto-select has already been initiated for the
current undefined llm_model state and skip re-running the effect if it has
already been triggered.

In `@frontend/app/settings/_components/ingest-settings-section.tsx`:
- Around line 127-139: The useEffect hook that sets the fallback embedding model
can execute multiple times because allEmbeddingOptions is recreated on every
render and included in the dependency array, causing handleEmbeddingModelChange
to be called repeatedly while settings.knowledge?.embedding_model is empty. Add
a one-shot guard using useRef to track whether the fallback has already been
applied, and only allow the fallback assignment to occur once. Check the ref at
the beginning of the effect to skip execution if the fallback has already been
set, and set the ref to true after the first successful call to
handleEmbeddingModelChange.

In `@src/api/settings/endpoints.py`:
- Around line 643-645: The `_first_configured_embedding_provider` function calls
at multiple locations (around lines 643, 675, and 732) can still return "openai"
as a fallback even when OpenAI is not configured or has been removed, resulting
in an invalid embedding_provider/credentials combination. Fix this by ensuring
that before assigning the result of `_first_configured_embedding_provider` to
`current_config.knowledge.embedding_provider`, you validate that the returned
provider is actually configured in the current configuration. If the returned
provider is not configured, either skip the assignment, select a different valid
provider, or raise an appropriate error to prevent persisting an invalid
configuration state.

In `@tests/unit/test_settings_provider_removal_defaults.py`:
- Line 8: Remove the unused import statement `import pytest` from the file
tests/unit/test_settings_provider_removal_defaults.py. This import is not
referenced anywhere in the test file and is causing a Ruff F401 linting error
that blocks the lint job from passing.

---

Nitpick comments:
In `@tests/unit/test_settings_provider_removal_defaults.py`:
- Around line 250-255: The `_simulate_embedding_removal` method in the test
currently only validates the happy-path scenario where a valid fallback
embedding provider exists. Add a new test case that configures only Anthropic as
the embedding provider (which is not embedding-capable), then calls
`_simulate_embedding_removal` to simulate removing that provider, and assert
that the method either raises an exception or explicitly rejects the fallback
instead of silently accepting an unconfigured provider. This regression test
ensures the code does not silently select invalid fallback providers when no
embedding-capable alternatives remain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c73dbc05-5338-411d-9112-ca55705c5338

📥 Commits

Reviewing files that changed from the base of the PR and between 3584585 and 64903ff.

📒 Files selected for processing (6)
  • frontend/app/settings/_components/agent-settings-section.tsx
  • frontend/app/settings/_components/ingest-settings-section.tsx
  • frontend/lib/constants.ts
  • src/api/settings/endpoints.py
  • src/api/settings/helpers.py
  • tests/unit/test_settings_provider_removal_defaults.py

Comment thread frontend/app/settings/_components/agent-settings-section.tsx Outdated
Comment on lines +127 to +139
const allEmbeddingOptions = groupedEmbeddingModels.flatMap((g) => g.options);

useEffect(() => {
if (
!settings.knowledge?.embedding_model &&
allEmbeddingOptions.length > 0
) {
const fallback =
allEmbeddingOptions.find((o) => o.default) || allEmbeddingOptions[0];
handleEmbeddingModelChange(fallback.value, fallback.provider);
}
}, [settings.knowledge?.embedding_model, allEmbeddingOptions]);

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:

cat -n frontend/app/settings/_components/ingest-settings-section.tsx | head -200

Repository: langflow-ai/openrag

Length of output: 8541


🏁 Script executed:

cat -n frontend/app/settings/_components/ingest-settings-section.tsx | tail -n +200 | head -100

Repository: langflow-ai/openrag

Length of output: 4069


🏁 Script executed:

rg "isPending" frontend/app/settings/_components/ -A 2 -B 2

Repository: langflow-ai/openrag

Length of output: 10244


🏁 Script executed:

rg "useUpdateSettingsMutation" -A 20 frontend/app/api/mutations/

Repository: langflow-ai/openrag

Length of output: 1847


🏁 Script executed:

rg "useEffect.*allEmbeddingOptions|const allEmbeddingOptions" frontend/app/settings/ -B 3 -A 5

Repository: langflow-ai/openrag

Length of output: 828


🏁 Script executed:

rg "lastAutoSelected|auto.*select.*fallback|flatMap.*options" frontend/ --type tsx --type ts

Repository: langflow-ai/openrag

Length of output: 91


🏁 Script executed:

rg "auto.*select|fallback.*effect|lastAutoSelected" frontend/app/settings/ -A 3 -B 2

Repository: langflow-ai/openrag

Length of output: 950


🏁 Script executed:

cat -n frontend/app/settings/_components/agent-settings-section.tsx | head -150

Repository: langflow-ai/openrag

Length of output: 6303


Add a one-shot guard to prevent repeated embedding fallback writes.

The fallback effect can repeatedly call handleEmbeddingModelChange(...) while settings.knowledge?.embedding_model is empty because allEmbeddingOptions (line 127) is recreated on every render and included in the dependency array (line 138). While the mutation is pending, the condition remains true and the effect can re-run, creating duplicate API calls and toasts.

💡 Suggested fix
+ import { useEffect, useRef, useState } from "react";
- import { useEffect, useState } from "react";
+ const lastAutoSelectedEmbeddingRef = useRef<string | null>(null);
  const allEmbeddingOptions = groupedEmbeddingModels.flatMap((g) => g.options);

  useEffect(() => {
+   if (settings.knowledge?.embedding_model) {
+     lastAutoSelectedEmbeddingRef.current = null;
+     return;
+   }
+   if (allEmbeddingOptions.length === 0 || updateSettingsMutation.isPending) return;
+
    const fallback =
      allEmbeddingOptions.find((o) => o.default) || allEmbeddingOptions[0];
-   if (
-     !settings.knowledge?.embedding_model &&
-     allEmbeddingOptions.length > 0
-   ) {
-     handleEmbeddingModelChange(fallback.value, fallback.provider);
-   }
+ const fallbackKey = `${fallback.provider}:${fallback.value}`;
+ if (lastAutoSelectedEmbeddingRef.current === fallbackKey) return;
+
+ lastAutoSelectedEmbeddingRef.current = fallbackKey;
+ handleEmbeddingModelChange(fallback.value, fallback.provider);
  }, [
    settings.knowledge?.embedding_model,
    allEmbeddingOptions,
+   updateSettingsMutation.isPending,
  ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allEmbeddingOptions = groupedEmbeddingModels.flatMap((g) => g.options);
useEffect(() => {
if (
!settings.knowledge?.embedding_model &&
allEmbeddingOptions.length > 0
) {
const fallback =
allEmbeddingOptions.find((o) => o.default) || allEmbeddingOptions[0];
handleEmbeddingModelChange(fallback.value, fallback.provider);
}
}, [settings.knowledge?.embedding_model, allEmbeddingOptions]);
import { useEffect, useRef, useState } from "react";
const lastAutoSelectedEmbeddingRef = useRef<string | null>(null);
const allEmbeddingOptions = groupedEmbeddingModels.flatMap((g) => g.options);
useEffect(() => {
if (settings.knowledge?.embedding_model) {
lastAutoSelectedEmbeddingRef.current = null;
return;
}
if (allEmbeddingOptions.length === 0 || updateSettingsMutation.isPending) return;
const fallback =
allEmbeddingOptions.find((o) => o.default) || allEmbeddingOptions[0];
const fallbackKey = `${fallback.provider}:${fallback.value}`;
if (lastAutoSelectedEmbeddingRef.current === fallbackKey) return;
lastAutoSelectedEmbeddingRef.current = fallbackKey;
handleEmbeddingModelChange(fallback.value, fallback.provider);
}, [
settings.knowledge?.embedding_model,
allEmbeddingOptions,
updateSettingsMutation.isPending,
]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/app/settings/_components/ingest-settings-section.tsx` around lines
127 - 139, The useEffect hook that sets the fallback embedding model can execute
multiple times because allEmbeddingOptions is recreated on every render and
included in the dependency array, causing handleEmbeddingModelChange to be
called repeatedly while settings.knowledge?.embedding_model is empty. Add a
one-shot guard using useRef to track whether the fallback has already been
applied, and only allow the fallback assignment to occur once. Check the ref at
the beginning of the effect to skip execution if the fallback has already been
set, and set the ref to true after the first successful call to
handleEmbeddingModelChange.

Comment on lines +643 to +645
fb = _first_configured_embedding_provider(current_config, "ollama")
current_config.knowledge.embedding_provider = fb
current_config.knowledge.embedding_model = _default_embedding_model(fb)

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 | ⚡ Quick win

Prevent embedding fallback from selecting an unconfigured provider.

At Line 643, Line 675, and Line 732, fallback embedding provider selection can still resolve to "openai" even when OpenAI was just removed (or never configured). That can persist an invalid embedding_provider/credentials combination in config.

Suggested fix
 # remove_ollama_config branch
 if current_config.knowledge.embedding_provider == "ollama":
     fb = _first_configured_embedding_provider(current_config, "ollama")
+    if not getattr(current_config.providers, fb).configured:
+        return JSONResponse(
+            {"error": "Cannot remove Ollama configuration: configure another embedding provider first."},
+            status_code=400,
+        )
     current_config.knowledge.embedding_provider = fb
     current_config.knowledge.embedding_model = _default_embedding_model(fb)

 # remove_openai_config branch
 if current_config.knowledge.embedding_provider == "openai":
     fb = _first_configured_embedding_provider(current_config, "openai")
+    if not getattr(current_config.providers, fb).configured:
+        return JSONResponse(
+            {"error": "Cannot remove OpenAI configuration: configure another embedding provider first."},
+            status_code=400,
+        )
     current_config.knowledge.embedding_provider = fb
     current_config.knowledge.embedding_model = _default_embedding_model(fb)

 # remove_watsonx_config branch
 if current_config.knowledge.embedding_provider == "watsonx":
     fb = _first_configured_embedding_provider(current_config, "watsonx")
+    if not getattr(current_config.providers, fb).configured:
+        return JSONResponse(
+            {"error": "Cannot remove IBM watsonx.ai configuration: configure another embedding provider first."},
+            status_code=400,
+        )
     current_config.knowledge.embedding_provider = fb
     current_config.knowledge.embedding_model = _default_embedding_model(fb)

Also applies to: 675-677, 732-734

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/settings/endpoints.py` around lines 643 - 645, The
`_first_configured_embedding_provider` function calls at multiple locations
(around lines 643, 675, and 732) can still return "openai" as a fallback even
when OpenAI is not configured or has been removed, resulting in an invalid
embedding_provider/credentials combination. Fix this by ensuring that before
assigning the result of `_first_configured_embedding_provider` to
`current_config.knowledge.embedding_provider`, you validate that the returned
provider is actually configured in the current configuration. If the returned
provider is not configured, either skip the assignment, select a different valid
provider, or raise an appropriate error to prevent persisting an invalid
configuration state.

Comment thread tests/unit/test_settings_provider_removal_defaults.py Outdated
@github-actions github-actions Bot added bug 🔴 Something isn't working. and removed bug 🔴 Something isn't working. labels Jun 22, 2026
@github-actions github-actions Bot added bug 🔴 Something isn't working. and removed bug 🔴 Something isn't working. labels Jun 22, 2026
@mpawlow mpawlow self-requested a review June 23, 2026 15:36
@mpawlow

mpawlow commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Functional Review 1

  • ✅ LGTM 🚀

@mpawlow mpawlow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review 1

  • ✅ Approved / LGTM 🚀
  • See PR comment (1a) for potential / optional consideration

[groupedLlmModels],
);

useEffect(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(1a) [Minor] Fallback useEffect can fire duplicate save mutations during incremental model loading

Problem

  • The new effects run handleModelChange / handleEmbeddingModelChange whenever !llm_model && options.length > 0.
  • allLlmOptions / allEmbeddingOptions are memoized off the per-provider query results. Those queries resolve at different times (OpenAI, Anthropic, Ollama, watsonx), so the memo identity changes several times during initial load.
  • Each identity change re-runs the effect. Until the in-flight updateSettingsMutation completes and settings is refetched, settings.agent?.llm_model is still empty, so the guard passes again and a second/third mutation fires.
  • Net effect: multiple redundant PATCH /settings calls (and multiple "Settings updated successfully" toasts) for a single intended default-selection.

Background Information

  • CodeRabbit flagged this; its stated premise ("groupedLlmModels isn't memoized") is now outdated since the PR memoizes both lists — but the residual race remains because the memo inputs themselves change as queries resolve.

Code References

  • frontend/app/settings/_components/ingest-settings-section.tsx:127-139 (approx.)

Potential Solution

  • Add a one-shot useRef guard so the auto-select fires at most once per empty-model state:
const autoSelectedLlm = useRef(false);
useEffect(() => {
  if (autoSelectedLlm.current) return;
  if (!settings.agent?.llm_model && allLlmOptions.length > 0) {
    autoSelectedLlm.current = true;
    const fallback = allLlmOptions.find((o) => o.default) || allLlmOptions[0];
    handleModelChange(fallback.value, fallback.provider);
  }
}, [settings.agent?.llm_model, allLlmOptions]);

Alternative Solutions

  • Gate the effect on !isLoadingAnyLlmModels so it only runs once all provider queries have settled (single, stable allLlmOptions).
  • Track updateSettingsMutation.isPending and skip while a mutation is in flight.

@github-actions github-actions Bot added the lgtm label Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend 🔷 Issues related to backend services (OpenSearch, Langflow, APIs) bug 🔴 Something isn't working. community frontend 🟨 Issues related to the UI/UX lgtm tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants