Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ LANGFLOW_DATABASE_URL=sqlite:///./langflow.db
LANGFLOW_ALEMBIC_LOG_TO_STDOUT=False


# mem0 creates a directory
# mem0 creates a directory
# for chat history, vector stores, and other artifacts
# its default path is ~/.mem0.
# we can change this path with
# we can change this path with
# environment variable "MEM0_DIR"
# Example: MEM0_DIR=/tmp/.mem0

# composio creates a cache directory
# composio creates a cache directory
# for file uploads/downloads.
# its default path is ~/.composio
# we can change this path with
# we can change this path with
# environment variable "COMPOSIO_CACHE_DIR"
# Example: COMPOSIO_CACHE_DIR=/tmp/.composio

Expand Down Expand Up @@ -140,6 +140,13 @@ LANGFLOW_STORE_ENVIRONMENT_VARIABLES=
# Default: true
LANGFLOW_MCP_COMPOSER_ENABLED=

# Live Model Data from models.dev API
# When enabled, fetches model data from models.dev API instead of static constants
# Only includes supported providers. Falls back to static if API fails.
# Values: true, false
# Default: false
LFX_USE_LIVE_MODEL_DATA=

# STORE_URL
# Example: LANGFLOW_STORE_URL=https://api.langflow.store
# LANGFLOW_STORE_URL=
Expand Down
541 changes: 212 additions & 329 deletions src/frontend/package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ export default function ModelInputComponent({
);

// Loading state
if (!options || options.length === 0 || refreshOptions) {
if ((!options || options?.length === 0) && hasEnabledProviders) {
return <div className="w-full">{renderLoadingButton()}</div>;
}

Expand Down
18 changes: 0 additions & 18 deletions src/frontend/src/constants/providerConstants.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
export const PROVIDER_VARIABLE_MAPPING: Record<string, string> = {
OpenAI: "OPENAI_API_KEY",
Anthropic: "ANTHROPIC_API_KEY",
"Google Generative AI": "GOOGLE_API_KEY",
Google: "GOOGLE_API_KEY",
Ollama: "OLLAMA_BASE_URL",
"IBM Watsonx": "WATSONX_APIKEY",
Cohere: "COHERE_API_KEY",
HuggingFace: "HUGGINGFACEHUB_API_TOKEN",
Groq: "GROQ_API_KEY",
Mistral: "MISTRAL_API_KEY",
Together: "TOGETHER_API_KEY",
Perplexity: "PERPLEXITYAI_API_KEY",
Bedrock: "AWS_ACCESS_KEY_ID",
AzureOpenAI: "AZURE_OPENAI_API_KEY",
VertexAI: "VERTEXAI_API_KEY",
};

/**
* Providers that don't require an API key to activate.
* These providers will show an "Activate" button instead of an API key input.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ModelProviderInfo {

export interface ModelProviderWithStatus extends ModelProviderInfo {
icon?: string;
documentation_url?: string;
}

export interface GetModelProvidersParams {
Expand Down Expand Up @@ -48,7 +49,6 @@ export const useGetModelProviders: useQueryFunctionType<

return providersData.map((providerInfo) => ({
...providerInfo,
icon: getProviderIcon(providerInfo.provider),
}));
} catch (error) {
console.error("Error fetching model providers:", error);
Expand All @@ -72,21 +72,3 @@ export const useGetModelProviders: useQueryFunctionType<

return queryResult;
};

// Helper function to map provider names to icon names
const getProviderIcon = (providerName: string): string => {
const iconMap: Record<string, string> = {
OpenAI: "OpenAI",
Anthropic: "Anthropic",
"Google Generative AI": "Google",
Groq: "Groq",
"Amazon Bedrock": "Bedrock",
NVIDIA: "NVIDIA",
Cohere: "Cohere",
"Azure OpenAI": "AzureOpenAI",
SambaNova: "SambaNova",
Ollama: "Ollama",
};

return iconMap[providerName] || "Bot";
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useQueryFunctionType } from "@/types/api";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";

export type ProviderVariableMapping = Record<string, string>;

export const useGetProviderVariableMapping: useQueryFunctionType<
undefined,
ProviderVariableMapping
> = (options) => {
const { query } = UseRequestProcessor();

const getProviderVariableMappingFn =
async (): Promise<ProviderVariableMapping> => {
try {
const url = `${getURL("MODELS")}/provider-variable-mapping`;
const response = await api.get<ProviderVariableMapping>(url);
return response.data;
} catch (error) {
console.error("Error fetching provider variable mapping:", error);
return {};
}
};

const queryResult = query(
["useGetProviderVariableMapping"],
getProviderVariableMappingFn,
{
refetchOnWindowFocus: false,
staleTime: 1000 * 60 * 5, // 5 minutes
...options,
},
);

return queryResult;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ jest.mock("@/components/common/genericIconComponent", () => ({
),
}));

// Mock PROVIDER_VARIABLE_MAPPING
jest.mock("@/constants/providerConstants", () => ({
PROVIDER_VARIABLE_MAPPING: {
OpenAI: "OPENAI_API_KEY",
Anthropic: "ANTHROPIC_API_KEY",
Cohere: "COHERE_API_KEY",
},
}));

const defaultProps = {
authName: "",
onAuthNameChange: jest.fn(),
Expand Down Expand Up @@ -70,14 +61,16 @@ describe("ModelProviderEdit", () => {
expect(screen.getByText(/Find your API key/)).toBeInTheDocument();
});

it("should display provider-specific variable name when providerName is set", () => {
render(<ModelProviderEdit {...defaultProps} providerName="OpenAI" />);
it("should display provider-specific variable name when variableName is set", () => {
render(
<ModelProviderEdit {...defaultProps} variableName="OPENAI_API_KEY" />,
);

const authInput = screen.getByTestId("auth-name-input");
expect(authInput).toHaveValue("OPENAI_API_KEY");
});

it("should display UNKNOWN_API_KEY when providerName is not set", () => {
it("should display UNKNOWN_API_KEY when variableName is not set", () => {
render(<ModelProviderEdit {...defaultProps} />);

const authInput = screen.getByTestId("auth-name-input");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ForwardedIconComponent from "@/components/common/genericIconComponent";
import { Input } from "@/components/ui/input";
import { PROVIDER_VARIABLE_MAPPING } from "@/constants/providerConstants";

export interface ModelProviderEditProps {
authName: string;
Expand All @@ -10,6 +9,7 @@ export interface ModelProviderEditProps {
apiBase: string;
onApiBaseChange: (value: string) => void;
providerName?: string;
variableName?: string;
}

/**
Expand All @@ -24,6 +24,7 @@ const ModelProviderEdit = ({
apiBase,
onApiBaseChange,
providerName, // Reserved for future provider-specific behavior
variableName,
}: ModelProviderEditProps) => {
return (
<div className="flex flex-col gap-4 p-4" data-testid="model-provider-edit">
Expand All @@ -36,11 +37,7 @@ const ModelProviderEdit = ({
</div>
<Input
placeholder="Authorization Name"
value={
providerName
? PROVIDER_VARIABLE_MAPPING[providerName]
: "UNKNOWN_API_KEY"
}
value={variableName || "UNKNOWN_API_KEY"}
disabled
onChange={(e) => onAuthNameChange(e.target.value)}
data-testid="auth-name-input"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ const ModelSelection = ({
const embeddingModels = availableModels.filter(
(model) => model.metadata?.model_type === "embeddings",
);
const imageModels = availableModels.filter(
(model) => model.metadata?.model_type === "image",
);
const audioModels = availableModels.filter(
(model) => model.metadata?.model_type === "audio",
);
const videoModels = availableModels.filter(
(model) => model.metadata?.model_type === "video",
);

const renderModelSection = (
title: string,
Expand Down Expand Up @@ -108,6 +117,9 @@ const ModelSelection = ({
embeddingModels,
"embeddings",
)}
{renderModelSection("Image Models", imageModels, "image")}
{renderModelSection("Audio Models", audioModels, "audio")}
{renderModelSection("Video Models", videoModels, "video")}
</>
) : modelType === "llm" ? (
renderModelSection("LLM Models", llmModels, "llm")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ProviderList = ({
is_enabled: provider.is_enabled,
model_count: matchingModels.length,
models: matchingModels,
documentation_url: provider.documentation_url,
};
})
.filter((provider) => provider.model_count > 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Provider = {
is_enabled: boolean;
model_count?: number;
models?: Model[];
documentation_url?: string;
};

/** Map of provider -> model_name -> enabled status */
Expand Down
27 changes: 20 additions & 7 deletions src/frontend/src/modals/modelProviderModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Dialog, DialogContent, DialogHeader } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import {
NO_API_KEY_PROVIDERS,
PROVIDER_VARIABLE_MAPPING,
VARIABLE_CATEGORY,
} from "@/constants/providerConstants";
import { useGetProviderVariableMapping } from "@/controllers/API/queries/models/use-get-provider-variable-mapping";
import { useUpdateEnabledModels } from "@/controllers/API/queries/models/use-update-enabled-models";
import {
useGetGlobalVariables,
Expand Down Expand Up @@ -53,6 +53,8 @@ const ModelProviderModal = ({
const { data: globalVariables = [] } = useGetGlobalVariables();
const { mutate: updateEnabledModels } = useUpdateEnabledModels();
const { refreshAllModelInputs } = useRefreshModelInputs();
const { data: providerVariableMapping = {} } =
useGetProviderVariableMapping();

const isPending = isCreating || isUpdating;

Expand Down Expand Up @@ -128,7 +130,7 @@ const ModelProviderModal = ({
if (!selectedProvider) return;

// Map provider name to its corresponding global variable name
const variableName = PROVIDER_VARIABLE_MAPPING[selectedProvider.provider];
const variableName = providerVariableMapping[selectedProvider.provider];
if (!variableName) {
setErrorData({
title: "Invalid Provider",
Expand Down Expand Up @@ -187,7 +189,7 @@ const ModelProviderModal = ({
const handleConfigureProvider = () => {
if (!selectedProvider || !apiKey.trim()) return;

const variableName = PROVIDER_VARIABLE_MAPPING[selectedProvider.provider];
const variableName = providerVariableMapping[selectedProvider.provider];
if (!variableName) {
setErrorData({
title: "Invalid Provider",
Expand Down Expand Up @@ -259,7 +261,7 @@ const ModelProviderModal = ({
<div className="flex flex-row w-full overflow-hidden">
<div
className={cn(
"flex border-r p-2 flex-col transition-all duration-300 h-[513px] ease-in-out",
"flex border-r p-2 flex-col transition-all duration-300 h-[513px] ease-in-out overflow-y-auto",
selectedProvider ? "w-1/3" : "w-full",
)}
>
Expand Down Expand Up @@ -292,9 +294,20 @@ const ModelProviderModal = ({
{requiresApiKey ? (
<>
Add your{" "}
<span className="underline cursor-pointer hover:text-primary">
{selectedProvider?.provider} API key
</span>{" "}
{selectedProvider?.documentation_url ? (
<a
href={selectedProvider.documentation_url}
target="_blank"
rel="noopener noreferrer"
className="underline cursor-pointer hover:text-primary"
>
{selectedProvider?.provider} API key
</a>
) : (
<span className="underline cursor-pointer hover:text-primary">
{selectedProvider?.provider} API key
</span>
)}{" "}
to enable these models
</>
) : (
Expand Down
2 changes: 1 addition & 1 deletion src/lfx/src/lfx/_assets/component_index.json

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions src/lfx/src/lfx/base/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
from .model import LCModelComponent
from .model_metadata import ModelCost, ModelLimits, ModelMetadata, ModelModalities, create_model_metadata
from .models_dev_client import (
clear_cache as clear_live_models_cache,
)
from .models_dev_client import (
fetch_models_dev_data,
get_live_models_detailed,
get_models_by_provider,
get_provider_metadata_from_api,
search_models,
)
from .unified_models import (
get_model_provider_variable_mapping,
get_model_providers,
get_unified_models_detailed,
refresh_live_model_data,
)

__all__ = [
# Core components
"LCModelComponent",
# Unified models API
"get_model_provider_variable_mapping",
"get_model_providers",
"get_unified_models_detailed",
"refresh_live_model_data",
# Model metadata types
"ModelCost",
"ModelLimits",
"ModelMetadata",
"ModelModalities",
"create_model_metadata",
# Live models API (models.dev)
"clear_live_models_cache",
"fetch_models_dev_data",
"get_live_models_detailed",
"get_models_by_provider",
"get_provider_metadata_from_api",
"search_models",
]

Check failure on line 41 in src/lfx/src/lfx/base/models/__init__.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.13)

Ruff (RUF022)

src/lfx/src/lfx/base/models/__init__.py:20:11: RUF022 `__all__` is not sorted
Comment on lines 20 to 41
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix: __all__ is not sorted (pipeline failure).

The static analysis is failing because __all__ entries are not sorted alphabetically. While the grouping comments are helpful for organization, Ruff's RUF022 rule requires sorted exports.

Apply this diff to fix the pipeline failure:

 __all__ = [
-    # Core components
     "LCModelComponent",
-    # Unified models API
-    "get_model_provider_variable_mapping",
-    "get_model_providers",
-    "get_unified_models_detailed",
-    "refresh_live_model_data",
-    # Model metadata types
     "ModelCost",
     "ModelLimits",
     "ModelMetadata",
     "ModelModalities",
+    "clear_live_models_cache",
     "create_model_metadata",
-    # Live models API (models.dev)
-    "clear_live_models_cache",
     "fetch_models_dev_data",
     "get_live_models_detailed",
+    "get_model_provider_variable_mapping",
+    "get_model_providers",
     "get_models_by_provider",
     "get_provider_metadata_from_api",
+    "get_unified_models_detailed",
+    "refresh_live_model_data",
     "search_models",
 ]
📝 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
__all__ = [
# Core components
"LCModelComponent",
# Unified models API
"get_model_provider_variable_mapping",
"get_model_providers",
"get_unified_models_detailed",
"refresh_live_model_data",
# Model metadata types
"ModelCost",
"ModelLimits",
"ModelMetadata",
"ModelModalities",
"create_model_metadata",
# Live models API (models.dev)
"clear_live_models_cache",
"fetch_models_dev_data",
"get_live_models_detailed",
"get_models_by_provider",
"get_provider_metadata_from_api",
"search_models",
]
__all__ = [
"LCModelComponent",
"ModelCost",
"ModelLimits",
"ModelMetadata",
"ModelModalities",
"clear_live_models_cache",
"create_model_metadata",
"fetch_models_dev_data",
"get_live_models_detailed",
"get_model_provider_variable_mapping",
"get_model_providers",
"get_models_by_provider",
"get_provider_metadata_from_api",
"get_unified_models_detailed",
"refresh_live_model_data",
"search_models",
]
🧰 Tools
🪛 GitHub Actions: Ruff Style Check

[error] 20-20: RUF022 __all__ is not sorted.

🪛 GitHub Check: Ruff Style Check (3.13)

[failure] 20-41: Ruff (RUF022)
src/lfx/src/lfx/base/models/init.py:20:11: RUF022 __all__ is not sorted

🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/models/__init__.py around lines 20 to 41, the __all__
list is failing RUF022 because its string entries are not alphabetically sorted;
update the list so all exported names (ignore the inline grouping comments) are
sorted in ascending alphabetical order, keeping each entry as a quoted string
with trailing commas and preserving overall formatting/line breaks.

Loading
Loading