feat: add is_active flag to model providers and update provider filtering#8588
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis change introduces an Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant AgentComponent
participant ModelConstants
User->>AgentComponent: Open agent configuration UI
AgentComponent->>ModelConstants: Request MODEL_PROVIDERS
ModelConstants-->>AgentComponent: Return only active providers
AgentComponent-->>User: Display filtered provider dropdown options
User->>AgentComponent: Select provider and configure agent
Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches🧪 Generate Unit Tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 2
🔭 Outside diff range comments (9)
src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json (3)
1934-1960: Critical: Mismatch betweenoptionsandoptions_metadatain City Selection Agent
Theoptionslist now correctly filters to["Anthropic", "Google Generative AI", "Groq", "OpenAI", "Custom"], butoptions_metadatastill contains icons for removed providers (Amazon, Azure, NVIDIA, SambaNova). This stale metadata will cause UI inconsistencies.
2699-2726: Critical: Mismatch betweenoptionsandoptions_metadatain Local Expert Agent
The dropdownoptionsare pruned to active providers, but the accompanyingoptions_metadatastill lists icons for deprecated providers. Please align metadata to match the new options array.
3464-3491: Critical: Mismatch betweenoptionsandoptions_metadatain Travel Concierge Agent
Similar to above, theoptions_metadataretains entries for removed providers. Update it to only include icons for the providers present inoptions.src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json (1)
1600-1610: UseMODEL_PROVIDERSinstead ofMODELS_METADATA.keys()in Custom branch
In the Custom‐provider branch ofupdate_build_config,options_metadatais built fromsorted(MODELS_METADATA.keys()), which can include inactive providers. It should pull metadata only for active ones:- options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{"icon": "brain"}], + options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{"icon": "brain"}],src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json (1)
1456-1545: Inconsistent provider metadata generation in custom pathThe initial
agent_llmdropdown builds itsoptions_metadatafromsorted(MODEL_PROVIDERS), but in the Custom branch ofupdate_build_configyou’re usingsorted(MODELS_METADATA.keys()), which can re-introduce inactive or deprecated providers. To keep only active providers, align the Custom branch to usesorted(MODEL_PROVIDERS)(and their metadata) instead of all keys inMODELS_METADATA.Also, double-check that every imported constant (
MODEL_PROVIDERS_DICT,MODEL_PROVIDERS,MODELS_METADATA,ALL_PROVIDER_FIELDS,MODEL_DYNAMIC_UPDATE_FIELDS) is actually used in this code snippet—remove any unused imports to keep the snippet lean.src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json (2)
1282-1314: Mismatch between dropdown options and metadata
In the Agent node’s static template, the"options_metadata"array foragent_llmstill includes entries for inactive providers (e.g., Amazon, Azure, NVIDIA, SambaNova), even though they’ve been removed from the"options"list. This will lead to orphaned metadata and UI inconsistencies.
Please trim"options_metadata"so it only contains icons for the providers actually listed under"options"([*sorted(MODEL_PROVIDERS), "Custom"]).
1470-1510: Custom branch uses wrong metadata source
In the Pythonupdate_build_configmethod, the Custom-dropdown logic buildsoptions_metadatafromsorted(MODELS_METADATA.keys()), which pulls in all providers (including inactive ones). It should mirror the active set—i.e.[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{"icon": "brain"}]—to avoid resurrecting deprecated providers in the UI.src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json (1)
355-362: Fix metadata mismatch in custom LLM dropdown
In theCustombranch ofupdate_build_config,options_metadatacurrently iterates oversorted(MODELS_METADATA.keys()), which may include inactive providers and doesn’t match theoptionslist. This leads to a length mismatch in the UI dropdown.Please apply this diff:
- options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{"icon": "brain"}], + options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{"icon": "brain"}],src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json (1)
2556-2589: Inconsistentoptions_metadataentries
The JSONoptions_metadatafor"agent_llm"still includes icons for providers removed from theoptionslist (e.g., Amazon, Azure, NVIDIA, SambaNova). This mismatch will lead to orphaned metadata in the frontend.Please remove metadata entries for inactive providers so the
options_metadataarray aligns exactly with theoptionslist.
♻️ Duplicate comments (3)
src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json (2)
1160-1220: Duplicate of dynamic provider dropdown update.See the comment on lines 480–540 for aligning the dropdown options and metadata without re-sorting.
2680-2740: Duplicate of dynamic provider dropdown update.See the comment on lines 480–540 for aligning the dropdown options and metadata without re-sorting.
src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json (1)
3388-3427: Duplicate UI mismatch
This second"agent_llm"definition also lists inactive providers in itsoptions_metadata. Apply the same cleanup here to maintain consistency across both agent nodes.
🧹 Nitpick comments (9)
src/backend/base/langflow/base/models/model_input_constants.py (1)
186-205: Consider collapsing duplicated provider metadata into a helper to avoid drift.Every provider block now sets
"is_active": <bool>. Copy-pasting these literal dictionaries makes it easy to forget updating one block (e.g. icon, prefix) when refactoring.- MODEL_PROVIDERS_DICT["OpenAI"] = { - "fields": openai_fields, - ... - "icon": OpenAIModelComponent.icon, - "is_active": True, - } + _register_provider( + name="OpenAI", + fields=openai_fields, + inputs=openai_inputs, + component_cls=OpenAIModelComponent, + active=True, + )A thin helper like
_register_provider(**kwargs)will trim ~80 LOC and guarantee consistent keys across providers.Also applies to: 210-217, 224-230, 236-243, 249-256, 262-269, 274-282
src/backend/base/langflow/components/agents/agent.py (1)
44-49: Dropdown options & metadata stay aligned – edge-case covered, but helper would de-duplicate.Appending
"Custom"and a{"icon": "brain"}placeholder keeps indices in sync – good catch.
To avoid replicating this logic again (seeupdate_build_config), extract a small helper:def _provider_dropdown(): options = [*sorted(MODEL_PROVIDERS), "Custom"] metadata = [MODELS_METADATA[k] for k in sorted(MODELS_METADATA)] + [{"icon": "brain"}] return options, metadataMakes the intent crystal-clear and prevents accidental divergence.
src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json (1)
1477-1492: Curated active providers inagent_llmdropdown
The staticoptionslist now contains only active providers and omits deprecated ones. Ensure this array remains in sync withMODEL_PROVIDERSinmodel_input_constants.pyto prevent drift between code and starter projects.src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json (1)
480-540: Preserve provider list order instead of sorting alphabetically.Sorting
MODEL_PROVIDERSmay reorder dropdown options in a way that diverges from the intended active providers sequence. To maintain the order defined by theis_activeflag in the constant, useMODEL_PROVIDERSdirectly:-options=[*sorted(MODEL_PROVIDERS), "Custom"], -options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{"icon": "brain"}], +options=[*MODEL_PROVIDERS, "Custom"], +options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS] + [{"icon": "brain"}],src/backend/base/langflow/initial_setup/starter_projects/Search agent.json (3)
1211-1211: Ensure Custom branch metadata aligns with filtered providers
In the Custom-provider branch,options_metadatais built fromsorted(MODELS_METADATA.keys()), which could contain inactive entries. Switch this tosorted(MODEL_PROVIDERS)to stay in sync with the active list.
1211-1211: Group and order imports for readability
The import block intermixes multiple internal modules. For clarity, reorder per PEP8: standard library first, then third-party, thenlangflow.*internal imports, each group alphabetized.
1211-1211: Cacheprovider_configsto reduce recomputation
provider_configsis rebuilt on every call toupdate_build_config. Extract it to a class‐level constant or helper to avoid reallocation on each invocation.src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json (1)
1125-1290: Filter provider_configs by active MODEL_PROVIDERSIn
update_build_config, the comprehension forprovider_configscurrently iterates over all entries inMODEL_PROVIDERS_DICT, which may include inactive providers. To avoid unnecessary processing and potential drift, consider restricting it to the active set:-provider_configs: dict[str, tuple[dict, list[dict]]] = { - provider: (...) - for provider in MODEL_PROVIDERS_DICT -} +provider_configs: dict[str, tuple[dict, list[dict]]] = { + provider: (...) + for provider in MODEL_PROVIDERS +}src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json (1)
1899-1905: Typo and Capitalization in Defaultsystem_prompt
The new Pokédex prompt contains a few minor errors:
- “pokédex” should be capitalized (“Pokédex”).
- Use “Pokémon” (or “pokemon”) rather than “pokemons.”
- Correct “mispelling” to “misspelling.”
Suggested refinement:
"You are a Pokédex agent. Fetch information about Pokémon using the endpoint:\nhttps://pokeapi.co/api/v2/pokemon/<pokemon_name>\n\nFor example:\nhttps://pokeapi.co/api/v2/pokemon/ditto\nhttps://pokeapi.co/api/v2/pokemon/pikachu\n\nFix user Pokémon name misspellings."
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/backend/base/langflow/base/models/model_input_constants.py(9 hunks)src/backend/base/langflow/components/agents/agent.py(3 hunks)src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json(2 hunks)src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Market Research.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Search agent.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json(3 hunks)src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json(1 hunks)src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json(3 hunks)src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json(1 hunks)
🔇 Additional comments (22)
src/backend/base/langflow/base/models/model_input_constants.py (1)
22-22: Good addition –is_activeflag formalised in schema.Explicitly capturing the activation state in the TypedDict prevents silent
KeyErrors and documents intent.src/backend/base/langflow/components/agents/agent.py (1)
223-230: Minor: metadata for “Custom” option differs between constructor & rebuild path.In the constructor the “Custom” entry re-uses the brain icon; here you build
options_metadatafromMODELS_METADATA(already filtered) and also append{"icon": "brain"}but without the associated “Custom” label inMODELS_METADATA. Works only because both lists are appended in the same order.Calling the same helper suggested above will guarantee consistency and avoid subtle off-by-one bugs if either list is re-ordered.
src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json (2)
2023-2028: Imported MODEL_PROVIDERS & MODELS_METADATA for dynamic configuration
The constantsMODEL_PROVIDERSandMODELS_METADATAare correctly added to the import, matching the refactor to global provider lists.
2049-2053: Switchedagent_llmdropdown to dynamic MODEL_PROVIDERS
Usingoptions=[*sorted(MODEL_PROVIDERS), "Custom"]with metadata fromMODELS_METADATAensures the code-driven list stays in sync with active providers.src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json (3)
1493-1510: Metadata aligned with curated providers
Theoptions_metadataentries match the curatedoptionslist and end with the default"brain"icon for custom selections. Confirm the order and count here mirror theoptionsabove.
1533-1541: Imports updated for dynamic provider filtering
The code string now importsMODEL_PROVIDERS,MODEL_PROVIDERS_DICT,MODELS_METADATA,MODEL_DYNAMIC_UPDATE_FIELDS, andALL_PROVIDER_FIELDSfrommodel_input_constants.py, laying the groundwork for active-only provider logic.
1557-1568: Agent inputs now driven byMODEL_PROVIDERS
AgentComponent.inputsleverages[*sorted(MODEL_PROVIDERS), "Custom"]and corresponding metadata comprehension to expose only active model providers at runtime. This centralizes provider configuration appropriately.src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json (2)
833-841: Import MODEL_PROVIDERS constant
The addition ofMODEL_PROVIDERSalongsideMODEL_PROVIDERS_DICTensures we can leverage the centralized, active-only provider list throughout the component. Confirm that every dynamic reference to providers (e.g., in dropdown options or metadata generation) consistently usesMODEL_PROVIDERS.
792-800: Filter out inactive providers in dropdown configuration
Theagent_llm.optionsarray has been updated to include only active providers plus"Custom", matching the curatedMODEL_PROVIDERSset. This aligns the starter-project defaults with the active provider list—just remember to keep this JSON in sync if you ever adjustMODEL_PROVIDERS.src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json (1)
1093-1093: Verify import and usage of MODEL_PROVIDERS
You’ve introducedMODEL_PROVIDERSalongsideMODEL_PROVIDERS_DICTto drive the dropdown options. Ensure that bothoptions=[*sorted(MODEL_PROVIDERS), "Custom"]andoptions_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{"icon": "brain"}]correctly reflect only active providers and that no deprecated entries remain.src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json (1)
1496-1503: ImportMODEL_PROVIDERSto drive dropdown filtering
ImportingMODEL_PROVIDERSalongsideMODEL_PROVIDERS_DICTandMODELS_METADATAcorrectly scopes the dropdown to active providers only, matching the newis_activeflag behavior.src/backend/base/langflow/initial_setup/starter_projects/Search agent.json (1)
1211-1211: Dynamic provider options and metadata usage is correct
Using[*sorted(MODEL_PROVIDERS), "Custom"]for the dropdown and[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{"icon": "brain"}]for metadata cleanly surfaces only active providers.src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json (1)
266-273: Properly importedMODEL_PROVIDERS
The addition ofMODEL_PROVIDERSto the import block allows you to filter the dropdown to active providers only. This aligns with the newis_activeflag in your model definitions.src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json (2)
1727-1740: Verify new imports and constant availability
You’ve addedMODEL_PROVIDERSalongsideMODEL_PROVIDERS_DICTandMODELS_METADATA(and introducedStructuredToolfromlangchain_core.tools). Ensure those symbols are actually exported inlangflow.base.models.model_input_constantsand thatlangchain_coreis a valid dependency in this context. A quick import‐only unit test or REPL check would catch any missing exports or package issues.
1750-1770: Dropdown now driven by curatedMODEL_PROVIDERS
Using[*sorted(MODEL_PROVIDERS), "Custom"]foroptions(and mappingoptions_metadataonly overMODEL_PROVIDERS) properly hides inactive providers and keeps the dropdown in sync with the new flag. This change cleanly aligns the starter project config with the active-only provider list.src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json (1)
1125-1290: Correct integration of active-only model providersThe
AgentComponentcode has been updated to import and useMODEL_PROVIDERSfor theagent_llmdropdown options and metadata, replacing the previousMODEL_PROVIDERS_DICT.keys()approach. This ensures only active providers are displayed and aligns with the new centralized constants. The related methods (get_llm,_build_llm_model, andupdate_build_config) have also been refactored to work with the filtered list without altering existing logic or signatures.src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json (2)
2238-2246: Update imports for active model providers
You’ve correctly importedMODEL_PROVIDERSandMODELS_METADATAalongsideMODEL_PROVIDERS_DICTto enable filtering of active providers.
2260-2274: UseMODEL_PROVIDERSfor agent dropdown options
Switching theoptionslist and itsoptions_metadatasource toMODEL_PROVIDERSensures only active providers appear in the agent’s LLM selector. Good use ofsorted(MODEL_PROVIDERS)for deterministic ordering.src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json (3)
1434-1475: Import and Provider Constants Update
The embedded Python now importsMODEL_PROVIDERSandMODELS_METADATAalongsideMODEL_PROVIDERS_DICT. This centralizes active providers and their metadata, which is the intended behavior. Confirm that all these constants exist and are correctly exported frommodel_input_constants.py.
1476-1500: CentralizedMODEL_PROVIDERSinagent_llmInput
Switchingoptionsto[*sorted(MODEL_PROVIDERS), "Custom"]filters out inactive entries and ensures deterministic ordering. This aligns with the PR objective to expose only active providers. No issues detected here.
1628-1640: Custom‐Provider Dropdown Construction
In theelif field_value == "Custom"block,optionsis built fromMODEL_PROVIDERS, butoptions_metadatausessorted(MODELS_METADATA.keys()). Ensure these two sets remain in sync so icons line up with the actual dropdown entries for Custom mode.src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json (1)
1236-1243: Dynamic import of active provider constants
The new imports (MODEL_PROVIDERS,MODEL_PROVIDERS_DICT,MODELS_METADATA, etc.) cleanly replace hard-coded provider lists and metadata. This aligns with the PR objective of only surfacing active providers.
| # Expose only active providers | ||
| MODEL_PROVIDERS = [name for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)] | ||
|
|
||
| ALL_PROVIDER_FIELDS: list[str] = [ | ||
| field for prov in MODEL_PROVIDERS_DICT.values() if prov.get("is_active", True) for field in prov["fields"] | ||
| ] |
There was a problem hiding this comment.
🛠️ Refactor suggestion
ALL_PROVIDER_FIELDS may contain duplicates – use a set to keep list unique.
If two providers expose identically-named fields, the current list-comprehension will duplicate them:
-ALL_PROVIDER_FIELDS: list[str] = [
- field for prov in MODEL_PROVIDERS_DICT.values() if prov.get("is_active", True) for field in prov["fields"]
-]
+ALL_PROVIDER_FIELDS: list[str] = list(
+ {
+ field
+ for prov in MODEL_PROVIDERS_DICT.values()
+ if prov.get("is_active", True)
+ for field in prov["fields"]
+ }
+)Guarantees predictable look-ups and slightly cheaper in checks.
📝 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.
| # Expose only active providers | |
| MODEL_PROVIDERS = [name for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)] | |
| ALL_PROVIDER_FIELDS: list[str] = [ | |
| field for prov in MODEL_PROVIDERS_DICT.values() if prov.get("is_active", True) for field in prov["fields"] | |
| ] | |
| # Expose only active providers | |
| MODEL_PROVIDERS = [name for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)] | |
| ALL_PROVIDER_FIELDS: list[str] = list( | |
| { | |
| field | |
| for prov in MODEL_PROVIDERS_DICT.values() | |
| if prov.get("is_active", True) | |
| for field in prov["fields"] | |
| } | |
| ) |
🤖 Prompt for AI Agents
In src/backend/base/langflow/base/models/model_input_constants.py around lines
286 to 291, the ALL_PROVIDER_FIELDS list may contain duplicate field names
because it aggregates fields from all active providers without filtering
duplicates. To fix this, convert the list comprehension to build a set instead,
ensuring uniqueness of fields. Then, convert the set back to a list if needed to
maintain the original data type, which will guarantee unique fields and improve
lookup efficiency.
| "title_case": false, | ||
| "type": "code", | ||
| "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers.current_date import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n # note the tools are not required to run the agent, hence the validation removed.\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v is not None}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\", tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n" | ||
| "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers.current_date import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n # note the tools are not required to run the agent, hence the validation removed.\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v is not None}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\", tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Ensure Custom provider metadata aligns with active providers
In the Custom branch you’re constructing options_metadata from sorted(MODELS_METADATA.keys()), which may include deprecated or inactive providers. To stay consistent with the dropdown options (which use MODEL_PROVIDERS), switch to filtering metadata by the same list.
Apply this diff:
- options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]
- + [{ "icon": "brain" }],
+ options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)]
+ + [{ "icon": "brain" }],📝 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.
| "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers.current_date import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n # note the tools are not required to run the agent, hence the validation removed.\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v is not None}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\", tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n" | |
| elif field_value == "Custom": | |
| # Delete all provider fields | |
| self.delete_fields(build_config, ALL_PROVIDER_FIELDS) | |
| # Update with custom component | |
| custom_component = DropdownInput( | |
| name="agent_llm", | |
| display_name="Language Model", | |
| options=[*sorted(MODEL_PROVIDERS), "Custom"], | |
| value="Custom", | |
| real_time_refresh=True, | |
| input_types=["LanguageModel"], | |
| options_metadata=[MODELS_METADATA[key] for key in sorted(MODEL_PROVIDERS)] | |
| + [{"icon": "brain"}], | |
| ) | |
| build_config.update({"agent_llm": custom_component.to_dict()}) |
🤖 Prompt for AI Agents
In src/backend/base/langflow/initial_setup/starter_projects/Market Research.json
at line 1314, the options_metadata for the Custom provider dropdown is built
using sorted(MODELS_METADATA.keys()), which may include deprecated or inactive
providers. To fix this, change the options_metadata construction to filter
MODELS_METADATA by the sorted keys of MODEL_PROVIDERS instead, ensuring
consistency with the dropdown options.
|
@Cristhianzl Is the CI failing Due to the freeze error? |
ogabrielluiz
left a comment
There was a problem hiding this comment.
We should iterate only once to get the actives then all other iterations can be done using the filtered dict.
@coderabbitai Looking forward to your suggestions on this comment. Does improvements possible in model_input_constants.py ? Suggestion on how to proceed with the comment. Planning to build ACTIVE_MODEL_PROVIDERS_DICT once and reuse it. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain@edwinjosechittilappilly, excellent optimization opportunity! Looking at the current implementation, I can see multiple filtering operations happening: # Current approach - multiple iterations:
MODEL_PROVIDERS = [key for key, value in MODEL_PROVIDERS_DICT.items() if value.get("is_active", True)]
ALL_PROVIDER_FIELDS = {k: v for provider_dict in [value for key, value in MODEL_PROVIDERS_DICT.items() if value.get("is_active", True)] for k, v in provider_dict.get("fields", {}).items()}
MODELS_METADATA = {key: value for key, value in MODEL_PROVIDERS_DICT.items() if value.get("is_active", True)}Recommended optimization: # Build filtered dict once and reuse it
ACTIVE_MODEL_PROVIDERS_DICT = {
key: value for key, value in MODEL_PROVIDERS_DICT.items()
if value.get("is_active", True)
}
# Derive all other collections from the filtered dict
MODEL_PROVIDERS = list(ACTIVE_MODEL_PROVIDERS_DICT.keys())
ALL_PROVIDER_FIELDS = {
k: v for provider_dict in ACTIVE_MODEL_PROVIDERS_DICT.values()
for k, v in provider_dict.get("fields", {}).items()
}
MODELS_METADATA = ACTIVE_MODEL_PROVIDERS_DICT.copy()Benefits of this approach:
This optimization reduces the time complexity from O(3n) to O(n) for the filtering operations, and makes the code more maintainable by having a single source of truth for active providers. Would you like me to help plan the implementation of this optimization? 🏁 Script executed: Length of output: 10589 Perfect! Now I can see the exact optimization opportunity. You're absolutely right - there are 3 separate iterations through Current Implementation (3 iterations):# Current - 3 separate iterations with same filter condition
MODEL_PROVIDERS = [name for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)]
ALL_PROVIDER_FIELDS: list[str] = [
field for prov in MODEL_PROVIDERS_DICT.values() if prov.get("is_active", True) for field in prov["fields"]
]
MODELS_METADATA = {
key: {"icon": prov["icon"]} for key, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)
}Optimized Implementation (1 iteration):# Build filtered dict once - single iteration
ACTIVE_MODEL_PROVIDERS_DICT = {
name: prov for name, prov in MODEL_PROVIDERS_DICT.items()
if prov.get("is_active", True)
}
# Derive all collections from the filtered dict - no additional iterations through original dict
MODEL_PROVIDERS = list(ACTIVE_MODEL_PROVIDERS_DICT.keys())
ALL_PROVIDER_FIELDS: list[str] = [
field for prov in ACTIVE_MODEL_PROVIDERS_DICT.values() for field in prov["fields"]
]
MODELS_METADATA = {
key: {"icon": prov["icon"]} for key, prov in ACTIVE_MODEL_PROVIDERS_DICT.items()
}Benefits:
Additional Suggestion:You could also export # At the end of the file, add to __all__ if it exists, or create it:
__all__ = [
"MODEL_PROVIDERS_DICT",
"ACTIVE_MODEL_PROVIDERS_DICT", # New export
"MODEL_PROVIDERS",
"ALL_PROVIDER_FIELDS",
"MODELS_METADATA",
"MODEL_DYNAMIC_UPDATE_FIELDS"
]This optimization is particularly valuable since this module is likely imported by many agent components, so the performance gain will be multiplied across the application. Would you like me to help plan the code changes for this optimization? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain@edwinjosechittilappilly, let me review the current implementation to check if the optimizations have been applied correctly. 🏁 Script executed: Length of output: 10703 Perfect! ✅ The optimizations have been correctly implemented. Let me break down what I see: ✅ Optimization Successfully AppliedBefore (Multiple Iterations): # Previous approach - 3 separate iterations
MODEL_PROVIDERS = [name for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)]
ALL_PROVIDER_FIELDS = [field for prov in MODEL_PROVIDERS_DICT.values() if prov.get("is_active", True) for field in prov["fields"]]
MODELS_METADATA = {key: {"icon": prov["icon"]} for key, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)}After (Single Iteration + Reuse): # ✅ Build filtered dict once - single iteration
ACTIVE_MODEL_PROVIDERS_DICT: dict[str, ModelProvidersDict] = {
name: prov for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)
}
# ✅ Derive all collections from the filtered dict - no additional iterations
MODEL_PROVIDERS: list[str] = list(ACTIVE_MODEL_PROVIDERS_DICT.keys())
ALL_PROVIDER_FIELDS: list[str] = [field for prov in ACTIVE_MODEL_PROVIDERS_DICT.values() for field in prov["fields"]]
MODELS_METADATA = {name: {"icon": prov["icon"]} for name, prov in ACTIVE_MODEL_PROVIDERS_DICT.items()}✅ Verification Results
✅ Active Providers ConfigurationThe
This matches the changes seen in the agent components where the inactive providers were removed from dropdown options. 🎯 Additional Benefits Achieved
The implementation is excellent and follows best practices. The optimization is working exactly as intended! 🚀 |
|
@coderabbitai suggest merge confidence after reviewing the PR |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 93660 🏁 Script executed: Length of output: 72234 @edwinjosechittilappilly Based on my comprehensive analysis of the PR, I can provide a detailed merge confidence assessment: 🎯 MERGE CONFIDENCE: HIGH (85-90%)✅ Strengths Identified1. Excellent Implementation Quality
2. Comprehensive Consistency
3. Minimal Breaking Change Risk
|
| Risk Category | Level | Mitigation |
|---|---|---|
| Breaking Changes | Low | Backward compatible implementation |
| Performance Impact | Low | Improved (optimization achieved) |
| User Experience | Low | Cleaner provider selection |
| Test Coverage | Medium | May require test updates |
| Production Stability | Low | Well-contained changes |
🚀 Business Value
- Performance: Reduced computational overhead in provider filtering
- Maintainability: Single source of truth for active providers
- User Experience: Cleaner dropdown with only functional providers
- Code Quality: Excellent optimization implementation
Final Recommendation: ✅ APPROVE TO MERGE
The implementation is technically sound, performance-optimized, and consistently applied across all components. The risk level is low with high business value. Any potential test failures can be addressed in follow-up commits.
Confidence Level: 87/100 🎯
…ring (langflow-ai#8588) * update model constants * [autofix.ci] apply automated fixes * update model input constants to reuse the filtered data * Update test_agent_component.py * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…ring (langflow-ai#8588) * update model constants * [autofix.ci] apply automated fixes * update model input constants to reuse the filtered data * Update test_agent_component.py * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Summary by CodeRabbit
New Features
Bug Fixes
Style
Chores