Skip to content

Commit a93c64a

Browse files
authored
fix(frontend): prefer strong model as assistant composer default (#14058)
* fix(frontend): prefer strong model as assistant default * chore(agentic): remove orphaned assistant flows
1 parent 54c24c6 commit a93c64a

10 files changed

Lines changed: 692 additions & 8815 deletions

File tree

src/backend/base/langflow/agentic/flows/LangflowAssistant.json

Lines changed: 342 additions & 641 deletions
Large diffs are not rendered by default.

src/backend/base/langflow/agentic/flows/SystemMessageGen.json

Lines changed: 0 additions & 3287 deletions
This file was deleted.

src/backend/base/langflow/agentic/flows/TemplateAssistant.json

Lines changed: 0 additions & 4285 deletions
This file was deleted.

src/backend/base/langflow/agentic/flows/TranslationFlow.json

Lines changed: 210 additions & 438 deletions
Large diffs are not rendered by default.

src/backend/tests/unit/agentic/flows/test_mcp_security_snapshots.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/frontend/src/components/core/assistantPanel/components/__tests__/model-selector.test.tsx

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,128 @@ describe("ModelSelector", () => {
156156
});
157157
});
158158

159+
describe("out-of-the-box default model (LE-1767)", () => {
160+
it("should_auto_select_first_strong_model_when_a_weak_model_comes_first_in_catalog_order", () => {
161+
// Arrange — QA repro: weak model first in catalog order, no stored selection
162+
mockFilteredProviders = [
163+
{
164+
provider: "OpenAI",
165+
icon: "OpenAI",
166+
models: [{ model_name: "gpt-4o-mini" }, { model_name: "gpt-4o" }],
167+
},
168+
];
169+
170+
const onModelChange = jest.fn();
171+
172+
// Act — fresh user: nothing in localStorage → selectedModel is null
173+
render(
174+
<ModelSelector selectedModel={null} onModelChange={onModelChange} />,
175+
);
176+
177+
// Assert — the strong model is picked, not allModels[0]
178+
expect(onModelChange).toHaveBeenCalledWith(
179+
expect.objectContaining({ provider: "OpenAI", name: "gpt-4o" }),
180+
);
181+
expect(
182+
screen.queryByTestId("assistant-model-weak-hint"),
183+
).not.toBeInTheDocument();
184+
});
185+
186+
it("should_auto_select_strong_model_from_later_provider_when_first_provider_only_has_weak_models", () => {
187+
// Arrange — first provider offers only a weak SKU; strong model lives in the next provider
188+
mockFilteredProviders = [
189+
{
190+
provider: "OpenAI",
191+
icon: "OpenAI",
192+
models: [{ model_name: "gpt-4o-mini" }],
193+
},
194+
{
195+
provider: "Anthropic",
196+
icon: "Anthropic",
197+
models: [{ model_name: "claude-sonnet-4-20250514" }],
198+
},
199+
];
200+
201+
const onModelChange = jest.fn();
202+
203+
// Act
204+
render(
205+
<ModelSelector selectedModel={null} onModelChange={onModelChange} />,
206+
);
207+
208+
// Assert
209+
expect(onModelChange).toHaveBeenCalledWith(
210+
expect.objectContaining({
211+
provider: "Anthropic",
212+
name: "claude-sonnet-4-20250514",
213+
}),
214+
);
215+
});
216+
217+
it("should_fall_back_to_first_model_and_show_hint_when_every_enabled_model_is_weak", () => {
218+
// Arrange — user enabled only weak SKUs; there is no strong model to prefer
219+
mockFilteredProviders = [
220+
{
221+
provider: "OpenAI",
222+
icon: "OpenAI",
223+
models: [
224+
{ model_name: "gpt-4o-mini" },
225+
{ model_name: "gpt-4.1-nano" },
226+
],
227+
},
228+
];
229+
230+
const onModelChange = jest.fn();
231+
232+
// Act
233+
render(
234+
<ModelSelector selectedModel={null} onModelChange={onModelChange} />,
235+
);
236+
237+
// Assert — degrades to today's behavior: first enabled model, hint visible
238+
expect(onModelChange).toHaveBeenCalledWith(
239+
expect.objectContaining({ provider: "OpenAI", name: "gpt-4o-mini" }),
240+
);
241+
expect(
242+
screen.getByTestId("assistant-model-weak-hint"),
243+
).toBeInTheDocument();
244+
});
245+
246+
it("should_not_override_an_explicit_weak_selection_the_user_made", () => {
247+
// Arrange — the user deliberately picked the weak model; the default must not fight them
248+
mockFilteredProviders = [
249+
{
250+
provider: "OpenAI",
251+
icon: "OpenAI",
252+
models: [{ model_name: "gpt-4o-mini" }, { model_name: "gpt-4o" }],
253+
},
254+
];
255+
256+
const weakModel: AssistantModel = {
257+
id: "OpenAI-gpt-4o-mini",
258+
name: "gpt-4o-mini",
259+
provider: "OpenAI",
260+
displayName: "gpt-4o-mini",
261+
};
262+
263+
const onModelChange = jest.fn();
264+
265+
// Act
266+
render(
267+
<ModelSelector
268+
selectedModel={weakModel}
269+
onModelChange={onModelChange}
270+
/>,
271+
);
272+
273+
// Assert — selection is respected; the advisory hint still shows
274+
expect(onModelChange).not.toHaveBeenCalled();
275+
expect(
276+
screen.getByTestId("assistant-model-weak-hint"),
277+
).toBeInTheDocument();
278+
});
279+
});
280+
159281
describe("stale model from localStorage", () => {
160282
it("should_auto_select_first_available_model_when_selected_provider_is_no_longer_available", () => {
161283
// Arrange — only Anthropic is configured, but selectedModel is a stale OpenAI model from localStorage

src/frontend/src/components/core/assistantPanel/components/model-selector.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,35 @@ export function ModelSelector({
5555
);
5656
}, [enabledProviders]);
5757

58-
// Auto-select first available model if none selected or if the selected model
58+
// Catalog order can put a weak SKU first (LE-1767): default to the first
59+
// strong model; allModels[0] only when every enabled model is weak.
60+
const defaultModel = useMemo(
61+
() =>
62+
allModels.find((m) => classifyModelStrength(m.name) === "strong") ??
63+
allModels[0] ??
64+
null,
65+
[allModels],
66+
);
67+
68+
// Auto-select the default model if none selected or if the selected model
5969
// is no longer available (e.g., provider was removed or model was disabled)
6070
useEffect(() => {
61-
if (allModels.length === 0) return;
71+
if (!defaultModel) return;
6272

6373
const isSelectedModelValid =
6474
selectedModel && allModels.some((m) => m.id === selectedModel.id);
6575

6676
if (!isSelectedModelValid) {
67-
const defaultModel = allModels[0];
6877
onModelChange({
6978
id: defaultModel.id,
7079
name: defaultModel.name,
7180
provider: defaultModel.provider,
7281
displayName: defaultModel.displayName,
7382
});
7483
}
75-
}, [selectedModel, allModels, onModelChange]);
84+
}, [selectedModel, allModels, defaultModel, onModelChange]);
7685

77-
const currentModel = selectedModel || allModels[0] || null;
86+
const currentModel = selectedModel || defaultModel;
7887

7988
// Resolve the provider icon for the currently selected model
8089
const currentProviderIcon = useMemo(() => {
@@ -95,12 +104,8 @@ export function ModelSelector({
95104
setIsOpen(false);
96105
};
97106

98-
// Discreet UX hint: when the selected model is too small for agent loops
99-
// (heuristic in helpers/model-strength.ts), surface an inline italic note
100-
// next to the chip. Classification is advisory; the dropdown behaves the
101-
// same regardless of result. Re-evaluated on every change of currentModel.
102-
// Must stay above the early returns below — Rules of Hooks: hook count
103-
// can't change between renders, and isLoading flips false on first fetch.
107+
// Advisory hint only (dropdown behaves the same). Must stay above the early
108+
// returns below — Rules of Hooks: isLoading flips false after first fetch.
104109
const modelStrength = useMemo(
105110
() => classifyModelStrength(currentModel?.name ?? ""),
106111
[currentModel?.name],
@@ -135,9 +140,8 @@ export function ModelSelector({
135140
}
136141

137142
return (
138-
// gap-3: leaves room for the chip's focus ring (~4px outline + offset)
139-
// so the italic warning to the right doesn't visually overlap it after
140-
// the user clicks the dropdown and the button keeps keyboard focus.
143+
// gap-3 leaves room for the chip's focus ring so the italic warning
144+
// doesn't overlap it while the button keeps keyboard focus.
141145
<div className="flex items-center gap-3">
142146
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
143147
<DropdownMenuTrigger asChild>

src/frontend/src/controllers/API/queries/assistant/index.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/frontend/src/controllers/API/queries/assistant/use-system-message-gen.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

src/frontend/src/controllers/API/queries/assistant/use-template-assistant.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)