mirror of
https://github.com/hanzoai/flow.git
synced 2026-10-17 01:29:18 +00:00
fix: preserve api key configuration on flow export (#12129)
* fix: preserve api key configuration on flow export * fix individual component's field * [autofix.ci] apply automated fixes * unhide var name * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fetch relevant provider keys * update starter projects * update based on env var * [autofix.ci] apply automated fixes * fetch only env variables * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * update starter projects * fix ruff errors * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * don't remove api keys if chosen by user * remove redundant code * [autofix.ci] apply automated fixes * fix update build config * remove api keys refactor * only load values when exists in db * modify other components * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Template updates * [autofix.ci] apply automated fixes * Component index update * Fix frontend test * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * preserve var names * [autofix.ci] apply automated fixes * update caution for saving api keys --------- Co-authored-by: Hanzo Dev <[email protected]> Co-authored-by: Eric Hare <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Hanzo Dev
Eric Hare
parent
d7d9f55fd2
commit
20934c833f
@@ -68,6 +68,17 @@ def has_api_terms(word: str):
|
||||
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
|
||||
|
||||
|
||||
def _get_provider_from_template(template: dict) -> str | None:
|
||||
"""Return provider name from template's model field, if any."""
|
||||
model_field = template.get("model")
|
||||
if not isinstance(model_field, dict):
|
||||
return None
|
||||
raw = model_field.get("value")
|
||||
if isinstance(raw, list) and len(raw) > 0 and isinstance(raw[0], dict):
|
||||
return raw[0].get("provider")
|
||||
return None
|
||||
|
||||
|
||||
def remove_api_keys(flow: dict):
|
||||
"""Remove api keys from flow data."""
|
||||
for node in flow.get("data", {}).get("nodes", []):
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,15 +20,29 @@ const useFetchDataOnMount = (
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const template = node.template[name];
|
||||
if (
|
||||
(template?.real_time_refresh ||
|
||||
template?.refresh_button ||
|
||||
(node.tool_mode && name === "tools_metadata")) &&
|
||||
// options can be undefined but not an empty array
|
||||
(template?.options?.length ?? 0) === 0
|
||||
) {
|
||||
if (!template) return;
|
||||
|
||||
const isRealtimeOrRefresh =
|
||||
template.real_time_refresh ||
|
||||
template.refresh_button ||
|
||||
(node.tool_mode && name === "tools_metadata");
|
||||
|
||||
const hasOptions = (template.options?.length ?? 0) > 0;
|
||||
|
||||
const needApiKeyPrefill =
|
||||
name === "model" &&
|
||||
node.template?.api_key != null &&
|
||||
!node.template?.api_key?.value;
|
||||
|
||||
const shouldFetchOnMount =
|
||||
isRealtimeOrRefresh &&
|
||||
(!hasOptions ||
|
||||
(name === "api_key" && !template.value) ||
|
||||
needApiKeyPrefill);
|
||||
|
||||
if (shouldFetchOnMount) {
|
||||
mutateTemplate(
|
||||
template?.value,
|
||||
template.value,
|
||||
nodeId,
|
||||
node,
|
||||
setNodeClass,
|
||||
@@ -41,7 +55,7 @@ const useFetchDataOnMount = (
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
}, []); // Empty dependency array ensures that this effect runs only once, on mount
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default useFetchDataOnMount;
|
||||
|
||||
+16
-3
@@ -7,6 +7,7 @@ import { CommandItem } from "../../../../ui/command";
|
||||
import GlobalVariableModal from "../../../GlobalVariableModal/GlobalVariableModal";
|
||||
import { getPlaceholder } from "../../helpers/get-placeholder-disabled";
|
||||
import type { InputGlobalComponentType, InputProps } from "../../types";
|
||||
import { looksLikeVariableName } from "../../../../../utils/reactflowUtils";
|
||||
import InputComponent from "../inputComponent";
|
||||
import {
|
||||
useGlobalVariableValue,
|
||||
@@ -114,9 +115,21 @@ export default function InputGlobalComponent({
|
||||
/>
|
||||
);
|
||||
|
||||
// // Extract options list for better readability
|
||||
const variableOptions = typedGlobalVariables.map((variable) => variable.name);
|
||||
const selectedOption = loadFromDb && valueExists ? currentValue : "";
|
||||
let variableOptions = typedGlobalVariables.map((variable) => variable.name);
|
||||
|
||||
const isEnvVarName =
|
||||
password && currentValue && looksLikeVariableName(currentValue);
|
||||
if (
|
||||
(loadFromDb &&
|
||||
currentValue &&
|
||||
!valueExists &&
|
||||
!variableOptions.includes(currentValue)) ||
|
||||
(isEnvVarName && !variableOptions.includes(currentValue))
|
||||
) {
|
||||
variableOptions = [...variableOptions, currentValue];
|
||||
}
|
||||
|
||||
const selectedOption = loadFromDb || isEnvVarName ? currentValue : "";
|
||||
|
||||
if (!showParameter) {
|
||||
return null;
|
||||
|
||||
@@ -741,7 +741,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications";
|
||||
export const SUCCESS_BUILD = "Built successfully ✨";
|
||||
|
||||
export const ALERT_SAVE_WITH_API =
|
||||
"Caution: Unchecking this box only removes API keys from fields specifically designated for API keys.";
|
||||
"⚠️ Caution: Exporting this flow may expose sensitive credentials.";
|
||||
|
||||
export const SAVE_WITH_API_CHECKBOX = "Save with my API keys";
|
||||
export const EDIT_TEXT_MODAL_TITLE = "Edit Text";
|
||||
|
||||
@@ -57,7 +57,7 @@ const ExportModal = forwardRef(
|
||||
setOpen={setOpen}
|
||||
onSubmit={async () => {
|
||||
try {
|
||||
const flowToExport: FlowType = {
|
||||
let flowToExport: FlowType = {
|
||||
id: currentFlow!.id,
|
||||
data: currentFlow!.data!,
|
||||
description,
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { removeApiKeys } from "../reactflowUtils";
|
||||
|
||||
describe("removeApiKeys", () => {
|
||||
function makeFlow(template: Record<string, any>) {
|
||||
return {
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
type: "genericNode",
|
||||
data: {
|
||||
node: {
|
||||
template,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
it("preserves api_key when it is an env/global variable name", () => {
|
||||
const flow = makeFlow({
|
||||
api_key: {
|
||||
name: "api_key",
|
||||
value: "OPENAI_API_KEY",
|
||||
password: true,
|
||||
load_from_db: true,
|
||||
},
|
||||
openai_api_key: {
|
||||
name: "openai_api_key",
|
||||
value: "sk-test-123",
|
||||
password: true,
|
||||
load_from_db: false,
|
||||
},
|
||||
});
|
||||
|
||||
const result = removeApiKeys(flow);
|
||||
const template = result.data!.nodes[0].data.node!.template;
|
||||
|
||||
expect(template.api_key.value).toBe("OPENAI_API_KEY");
|
||||
expect(template.api_key.load_from_db).toBe(true);
|
||||
|
||||
expect(template.openai_api_key.value).toBe("");
|
||||
expect(template.openai_api_key.load_from_db).toBe(false);
|
||||
});
|
||||
|
||||
it("clears api_key when it contains a raw secret", () => {
|
||||
const flow = makeFlow({
|
||||
api_key: {
|
||||
name: "api_key",
|
||||
value: "sk-secret-123",
|
||||
password: true,
|
||||
load_from_db: false,
|
||||
},
|
||||
});
|
||||
|
||||
const result = removeApiKeys(flow);
|
||||
const template = result.data!.nodes[0].data.node!.template;
|
||||
|
||||
expect(template.api_key.value).toBe("");
|
||||
expect(template.api_key.load_from_db).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves non-password fields", () => {
|
||||
const flow = makeFlow({
|
||||
regular_field: {
|
||||
name: "regular_field",
|
||||
value: "keep-me",
|
||||
password: false,
|
||||
},
|
||||
});
|
||||
|
||||
const result = removeApiKeys(flow);
|
||||
const template = result.data!.nodes[0].data.node!.template;
|
||||
|
||||
expect(template.regular_field.value).toBe("keep-me");
|
||||
});
|
||||
});
|
||||
@@ -437,16 +437,32 @@ export function isValidConnection(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function looksLikeVariableName(value: unknown): boolean {
|
||||
if (typeof value !== "string" || !value.trim()) return false;
|
||||
return /^[A-Z][A-Z0-9_]*$/i.test(value.trim());
|
||||
}
|
||||
|
||||
export function removeApiKeys(flow: FlowType): FlowType {
|
||||
const cleanFLow = cloneDeep(flow);
|
||||
cleanFLow.data!.nodes.forEach((node) => {
|
||||
const cleanFlow = cloneDeep(flow);
|
||||
cleanFlow.data!.nodes.forEach((node) => {
|
||||
if (node.type !== "genericNode") return;
|
||||
for (const key in node.data.node!.template) {
|
||||
const field = node.data.node!.template[key];
|
||||
const template = node.data.node!.template;
|
||||
for (const key in template) {
|
||||
const field = template[key];
|
||||
|
||||
// Remove password fields
|
||||
if (field.password) {
|
||||
// Preserve env/global variable names for api_key so imported flows
|
||||
// can still resolve credentials, but strip any raw secrets.
|
||||
if (
|
||||
key === "api_key" &&
|
||||
((typeof field.value === "string" &&
|
||||
looksLikeVariableName(field.value)) ||
|
||||
field.load_from_db === true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
field.value = "";
|
||||
field.load_from_db = false;
|
||||
}
|
||||
|
||||
// Handle MCP server configurations
|
||||
@@ -455,12 +471,11 @@ export function removeApiKeys(flow: FlowType): FlowType {
|
||||
field.value &&
|
||||
typeof field.value === "object"
|
||||
) {
|
||||
// Type assertion is safe here as we've verified it's an object with runtime checks
|
||||
cleanMcpConfig(field.value as MCPServerValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
return cleanFLow;
|
||||
return cleanFlow;
|
||||
}
|
||||
|
||||
export function updateTemplate(
|
||||
|
||||
@@ -523,7 +523,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": "dummy_api_key"
|
||||
"value": ""
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
@@ -172,31 +173,36 @@ def get_provider_required_variable_keys(provider: str) -> list[str]:
|
||||
return [v["variable_key"] for v in variables if v.get("required")]
|
||||
|
||||
|
||||
def _get_all_provider_specific_field_names() -> set[str]:
|
||||
"""Return set of all field names used as mapping_field by any provider."""
|
||||
names: set[str] = set()
|
||||
for meta in model_provider_metadata.values():
|
||||
for v in meta.get("variables", []):
|
||||
mapping = v.get("component_metadata", {}).get("mapping_field")
|
||||
if mapping:
|
||||
names.add(mapping)
|
||||
return names
|
||||
|
||||
|
||||
def apply_provider_variable_config_to_build_config(
|
||||
build_config: dict,
|
||||
provider: str,
|
||||
) -> dict:
|
||||
"""Apply provider variable metadata to component build config fields.
|
||||
|
||||
This function updates the build config fields based on the provider's variable metadata
|
||||
stored in the `component_metadata` nested dict:
|
||||
- Sets `required` based on `component_metadata.required`
|
||||
- Sets `advanced` based on `component_metadata.advanced`
|
||||
- Sets `info` based on `component_metadata.info`
|
||||
- Sets `show` to True for fields that have a mapping_field for this provider
|
||||
|
||||
Args:
|
||||
build_config: The component's build configuration dict
|
||||
provider: The selected provider name (e.g., "OpenAI", "IBM WatsonX")
|
||||
|
||||
Returns:
|
||||
Updated build_config dict
|
||||
First hides all provider-specific fields (so switching e.g. IBM -> OpenAI
|
||||
does not leave IBM fields visible), then shows and configures only the
|
||||
current provider's fields.
|
||||
"""
|
||||
import os
|
||||
|
||||
provider_vars = get_provider_all_variables(provider)
|
||||
all_provider_fields = _get_all_provider_specific_field_names()
|
||||
for field_name in all_provider_fields:
|
||||
if field_name in build_config:
|
||||
build_config[field_name]["show"] = False
|
||||
build_config[field_name]["required"] = False
|
||||
|
||||
# Build a lookup by component_metadata.mapping_field
|
||||
provider_vars = get_provider_all_variables(provider)
|
||||
vars_by_field = {}
|
||||
for v in provider_vars:
|
||||
component_meta = v.get("component_metadata", {})
|
||||
@@ -224,23 +230,19 @@ def apply_provider_variable_config_to_build_config(
|
||||
if info:
|
||||
field_config["info"] = info
|
||||
|
||||
# Show the field since it's relevant to this provider
|
||||
field_config["show"] = True
|
||||
|
||||
# If no value is set, try to get from environment variable
|
||||
env_var_key = var_info.get("variable_key")
|
||||
if env_var_key:
|
||||
current_value = field_config.get("value")
|
||||
# Only set from env if field is empty/None
|
||||
if not current_value or (isinstance(current_value, str) and not current_value.strip()):
|
||||
env_value = os.environ.get(env_var_key)
|
||||
if env_value and env_value.strip():
|
||||
field_config["value"] = env_value
|
||||
logger.debug(
|
||||
"Set field %s from environment variable %s",
|
||||
field_name,
|
||||
env_var_key,
|
||||
)
|
||||
env_value = os.environ.get(env_var_key)
|
||||
if env_value and str(env_value).strip():
|
||||
field_config["value"] = env_var_key
|
||||
field_config["load_from_db"] = True
|
||||
logger.debug(
|
||||
"Set field %s to env var name %s (value resolved at runtime)",
|
||||
field_name,
|
||||
env_var_key,
|
||||
)
|
||||
|
||||
return build_config
|
||||
|
||||
@@ -269,6 +271,17 @@ def get_model_providers() -> list[str]:
|
||||
return sorted({md.get("provider", "Unknown") for group in MODELS_DETAILED for md in group})
|
||||
|
||||
|
||||
def get_provider_for_model_name(model_name: str) -> str:
|
||||
"""Return the provider for a model name by searching MODELS_DETAILED."""
|
||||
if not model_name or not isinstance(model_name, str):
|
||||
return ""
|
||||
for group in MODELS_DETAILED:
|
||||
for md in group:
|
||||
if md.get("name") == model_name:
|
||||
return md.get("provider", "") or ""
|
||||
return ""
|
||||
|
||||
|
||||
def get_unified_models_detailed(
|
||||
providers: list[str] | None = None,
|
||||
model_name: str | None = None,
|
||||
@@ -382,17 +395,58 @@ def get_unified_models_detailed(
|
||||
def get_api_key_for_provider(user_id: UUID | str | None, provider: str, api_key: str | None = None) -> str | None:
|
||||
"""Get API key from self.api_key or global variables.
|
||||
|
||||
When api_key is set to an environment variable name (e.g. ANTHROPIC_API_KEY),
|
||||
that name is resolved from os.environ or global variables so imported flows
|
||||
can reference credentials without storing the raw key.
|
||||
|
||||
Args:
|
||||
user_id: The user ID to look up global variables for
|
||||
provider: The provider name (e.g., "OpenAI", "Anthropic")
|
||||
api_key: An optional API key provided directly
|
||||
api_key: An optional API key provided directly, or an env var name to resolve
|
||||
|
||||
Returns:
|
||||
The API key if found, None otherwise
|
||||
"""
|
||||
# First check if user provided an API key directly
|
||||
if api_key:
|
||||
return api_key
|
||||
|
||||
# Resolve variable name (canonical or custom e.g. MY_OPENAI_API_KEY) from env or global vars
|
||||
def _resolve_var_name(var_name: str) -> str | None:
|
||||
env_value = os.environ.get(var_name)
|
||||
if env_value and env_value.strip():
|
||||
return env_value.strip()
|
||||
if user_id and not (isinstance(user_id, str) and user_id == "None"):
|
||||
|
||||
async def _get_by_var_name():
|
||||
async with session_scope() as session:
|
||||
variable_service = get_variable_service()
|
||||
if variable_service is None:
|
||||
return None
|
||||
try:
|
||||
return await variable_service.get_variable(
|
||||
user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
|
||||
name=var_name,
|
||||
field="",
|
||||
session=session,
|
||||
)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
value = run_until_complete(_get_by_var_name())
|
||||
if value and str(value).strip():
|
||||
return str(value).strip()
|
||||
return None
|
||||
|
||||
if api_key and api_key.strip():
|
||||
var_name = api_key.strip()
|
||||
# Names that look like env/global variables (e.g. MY_OPENAI_API_KEY): resolve from env/DB
|
||||
if var_name.replace("_", "").isalnum() and var_name[0].isalpha():
|
||||
resolved = _resolve_var_name(var_name)
|
||||
if resolved:
|
||||
return resolved
|
||||
# Unresolved variable name: don't use as literal key
|
||||
if re.match(r"^[A-Z][A-Z0-9_]*$", var_name):
|
||||
return None
|
||||
# Literal API key (e.g. sk-...)
|
||||
return var_name
|
||||
|
||||
# If no user_id or user_id is the string "None", we can't look up global variables
|
||||
if user_id is None or (isinstance(user_id, str) and user_id == "None"):
|
||||
|
||||
@@ -6,8 +6,10 @@ from collections.abc import Callable # noqa: TC003 - required at runtime for dy
|
||||
from typing import Any
|
||||
|
||||
from lfx.base.models.unified_models import (
|
||||
apply_provider_variable_config_to_build_config,
|
||||
get_language_model_options,
|
||||
get_llm,
|
||||
get_provider_for_model_name,
|
||||
update_model_options_in_build_config,
|
||||
)
|
||||
from lfx.custom.custom_component.component import Component
|
||||
@@ -116,7 +118,7 @@ class LambdaFilterComponent(Component):
|
||||
|
||||
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
|
||||
"""Dynamically update build config with user-filtered model options."""
|
||||
return update_model_options_in_build_config(
|
||||
build_config = update_model_options_in_build_config(
|
||||
component=self,
|
||||
build_config=build_config,
|
||||
cache_key_prefix="language_model_options",
|
||||
@@ -125,6 +127,19 @@ class LambdaFilterComponent(Component):
|
||||
field_value=field_value,
|
||||
)
|
||||
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
provider = ""
|
||||
if isinstance(current_model_value, list) and current_model_value:
|
||||
selected_model = current_model_value[0]
|
||||
provider = (selected_model.get("provider") or "").strip()
|
||||
if not provider and selected_model.get("name"):
|
||||
provider = get_provider_for_model_name(str(selected_model["name"]))
|
||||
|
||||
if provider:
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
|
||||
return build_config
|
||||
|
||||
def get_data_structure(self, data):
|
||||
"""Extract the structure of data, replacing values with their types."""
|
||||
if isinstance(data, list):
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from typing import Any
|
||||
|
||||
from lfx.base.models.unified_models import (
|
||||
apply_provider_variable_config_to_build_config,
|
||||
get_language_model_options,
|
||||
get_llm,
|
||||
get_provider_for_model_name,
|
||||
update_model_options_in_build_config,
|
||||
)
|
||||
from lfx.custom import Component
|
||||
@@ -136,7 +138,7 @@ class SmartRouterComponent(Component):
|
||||
|
||||
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
|
||||
"""Dynamically update build config with user-filtered model options."""
|
||||
return update_model_options_in_build_config(
|
||||
build_config = update_model_options_in_build_config(
|
||||
component=self,
|
||||
build_config=build_config,
|
||||
cache_key_prefix="language_model_options",
|
||||
@@ -145,6 +147,19 @@ class SmartRouterComponent(Component):
|
||||
field_value=field_value,
|
||||
)
|
||||
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
provider = ""
|
||||
if isinstance(current_model_value, list) and current_model_value:
|
||||
selected_model = current_model_value[0]
|
||||
provider = (selected_model.get("provider") or "").strip()
|
||||
if not provider and selected_model.get("name"):
|
||||
provider = get_provider_for_model_name(str(selected_model["name"]))
|
||||
|
||||
if provider:
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
|
||||
return build_config
|
||||
|
||||
def update_outputs(self, frontend_node: dict, field_name: str, field_value: Any) -> dict:
|
||||
"""Create a dynamic output for each category in the categories table."""
|
||||
if field_name in {"routes", "enable_else_output", "model"}:
|
||||
|
||||
@@ -17,6 +17,7 @@ from lfx.base.models.unified_models import (
|
||||
apply_provider_variable_config_to_build_config,
|
||||
get_language_model_options,
|
||||
get_llm,
|
||||
get_provider_for_model_name,
|
||||
update_model_options_in_build_config,
|
||||
)
|
||||
from lfx.base.models.watsonx_constants import IBM_WATSONX_URLS
|
||||
@@ -489,29 +490,21 @@ class AgentComponent(ToolCallingAgentComponent):
|
||||
)
|
||||
build_config = dotdict(build_config)
|
||||
|
||||
# Iterate over all providers in the MODEL_PROVIDERS_DICT
|
||||
if field_name == "model":
|
||||
# Update input types for all fields
|
||||
build_config = self.update_input_types(build_config)
|
||||
|
||||
# Show/hide provider-specific fields based on selected model
|
||||
# Get current model value - from field_value if model is being changed, otherwise from build_config
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
if isinstance(current_model_value, list) and len(current_model_value) > 0:
|
||||
selected_model = current_model_value[0]
|
||||
provider = selected_model.get("provider", "")
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
provider = ""
|
||||
if isinstance(current_model_value, list) and current_model_value:
|
||||
selected_model = current_model_value[0]
|
||||
provider = (selected_model.get("provider") or "").strip()
|
||||
if not provider and selected_model.get("name"):
|
||||
provider = get_provider_for_model_name(str(selected_model["name"]))
|
||||
|
||||
# Hide provider-specific fields by default before applying provider config
|
||||
for field in ["base_url_ibm_watsonx", "project_id"]:
|
||||
if field in build_config:
|
||||
build_config[field]["show"] = False
|
||||
build_config[field]["required"] = False
|
||||
if provider:
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
|
||||
# Apply provider variable configuration (advanced, required, info, env var fallback)
|
||||
if provider:
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
|
||||
# Validate required keys
|
||||
if field_name == "model":
|
||||
default_keys = [
|
||||
"code",
|
||||
"_type",
|
||||
|
||||
@@ -3,9 +3,11 @@ from typing import Any
|
||||
from lfx.base.embeddings.embeddings_class import EmbeddingsWithModels
|
||||
from lfx.base.embeddings.model import LCEmbeddingsModel
|
||||
from lfx.base.models.unified_models import (
|
||||
apply_provider_variable_config_to_build_config,
|
||||
get_api_key_for_provider,
|
||||
get_embedding_class,
|
||||
get_embedding_model_options,
|
||||
get_provider_for_model_name,
|
||||
get_unified_models_detailed,
|
||||
update_model_options_in_build_config,
|
||||
)
|
||||
@@ -34,7 +36,6 @@ class EmbeddingModelComponent(LCEmbeddingsModel):
|
||||
|
||||
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
|
||||
"""Dynamically update build config with user-filtered model options."""
|
||||
# Update model options
|
||||
build_config = update_model_options_in_build_config(
|
||||
component=self,
|
||||
build_config=build_config,
|
||||
@@ -44,20 +45,23 @@ class EmbeddingModelComponent(LCEmbeddingsModel):
|
||||
field_value=field_value,
|
||||
)
|
||||
|
||||
# Show/hide provider-specific fields based on selected model
|
||||
if field_name == "model" and isinstance(field_value, list) and len(field_value) > 0:
|
||||
selected_model = field_value[0]
|
||||
provider = selected_model.get("provider", "")
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
provider = ""
|
||||
if isinstance(current_model_value, list) and current_model_value:
|
||||
selected_model = current_model_value[0]
|
||||
provider = (selected_model.get("provider") or "").strip()
|
||||
if not provider and selected_model.get("name"):
|
||||
provider = get_provider_for_model_name(str(selected_model["name"]))
|
||||
|
||||
# Show/hide watsonx fields
|
||||
if provider:
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
|
||||
# Embedding-specific WatsonX toggles not covered by provider metadata
|
||||
is_watsonx = provider == "IBM WatsonX"
|
||||
build_config["base_url_ibm_watsonx"]["show"] = is_watsonx
|
||||
build_config["project_id"]["show"] = is_watsonx
|
||||
build_config["truncate_input_tokens"]["show"] = is_watsonx
|
||||
build_config["input_text"]["show"] = is_watsonx
|
||||
if is_watsonx:
|
||||
build_config["base_url_ibm_watsonx"]["required"] = True
|
||||
build_config["project_id"]["required"] = True
|
||||
if "truncate_input_tokens" in build_config:
|
||||
build_config["truncate_input_tokens"]["show"] = is_watsonx
|
||||
if "input_text" in build_config:
|
||||
build_config["input_text"]["show"] = is_watsonx
|
||||
|
||||
return build_config
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from lfx.base.models.unified_models import (
|
||||
apply_provider_variable_config_to_build_config,
|
||||
get_language_model_options,
|
||||
get_llm,
|
||||
get_provider_for_model_name,
|
||||
update_model_options_in_build_config,
|
||||
)
|
||||
from lfx.base.models.watsonx_constants import IBM_WATSONX_URLS
|
||||
@@ -122,21 +123,15 @@ class LanguageModelComponent(LCModelComponent):
|
||||
field_value=field_value,
|
||||
)
|
||||
|
||||
# Hide all provider-specific fields by default
|
||||
for field in ["api_key", "base_url_ibm_watsonx", "project_id", "ollama_base_url"]:
|
||||
if field in build_config:
|
||||
build_config[field]["show"] = False
|
||||
build_config[field]["required"] = False
|
||||
|
||||
# Show/configure provider-specific fields based on selected model
|
||||
# Get current model value - from field_value if model is being changed, otherwise from build_config
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
if isinstance(current_model_value, list) and len(current_model_value) > 0:
|
||||
provider = ""
|
||||
if isinstance(current_model_value, list) and current_model_value:
|
||||
selected_model = current_model_value[0]
|
||||
provider = selected_model.get("provider", "")
|
||||
provider = (selected_model.get("provider") or "").strip()
|
||||
if not provider and selected_model.get("name"):
|
||||
provider = get_provider_for_model_name(str(selected_model["name"]))
|
||||
|
||||
if provider:
|
||||
# Apply provider variable configuration (required_for_component, advanced, env var fallback)
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
if provider:
|
||||
build_config = apply_provider_variable_config_to_build_config(build_config, provider)
|
||||
|
||||
return build_config
|
||||
|
||||
Reference in New Issue
Block a user