From ca595ac7fbd4e8022ac941fd766c71c39856a664 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 18 Jul 2025 15:03:17 -0300 Subject: [PATCH] feat: add module and code hash to component's metadata (#8737) * feat: filter out base components path in custom component loading * revert changes to allow testing the function * feat: add module_name parameter to custom component template functions * feat: include full module name in component template creation * feat: add module metadata to various starter project JSON files * feat: add code hash generation for custom component templates * test: add unit tests for code hash and module metadata functionality * feat: add code hash to metadata in various starter project JSON files * feat: refactor code hash generation to accept source code and class name * feat: implement loading strategy for custom components in settings service * Update metadata for various starter project JSON files to include code hashes and module references for ChatInput and ChatOutput components. This enhances traceability and consistency across the project configurations. * refactor: improve _generate_code_hash function for better error handling and clarity - Simplified the logic to generate a SHA256 hash of the source code. - Added explicit error handling for empty source code, encoding issues, and type errors. - Updated docstring to reflect changes in argument descriptions and added information about raised exceptions. * test: add unit tests for metadata functionality in custom utils - Introduced tests for the _generate_code_hash function, covering basic hash generation, error handling for empty and None source, hash consistency, and uniqueness for different code. - Added tests to verify metadata addition in template building functions, ensuring that module names and code hashes are correctly included in the metadata of custom components. --------- Co-authored-by: Edwin Jose --- src/backend/base/langflow/custom/utils.py | 52 ++++++- .../Basic Prompt Chaining.json | 10 +- .../starter_projects/Basic Prompting.json | 15 +- .../starter_projects/Blog Writer.json | 25 ++- .../Custom Component Generator.json | 20 ++- .../starter_projects/Document Q&A.json | 20 ++- .../Financial Report Parser.json | 15 +- .../starter_projects/Hybrid Search RAG.json | 30 +++- .../Image Sentiment Analysis.json | 20 ++- .../Instagram Copywriter.json | 35 ++++- .../starter_projects/Invoice Summarizer.json | 20 ++- .../starter_projects/Market Research.json | 20 ++- .../starter_projects/Meeting Summary.json | 45 ++++-- .../starter_projects/Memory Chatbot.json | 20 ++- .../starter_projects/News Aggregator.json | 20 ++- .../starter_projects/Nvidia Remix.json | 25 ++- .../starter_projects/Pokédex Agent.json | 15 +- .../Portfolio Website Code Generator.json | 15 +- .../starter_projects/Price Deal Finder.json | 20 ++- .../starter_projects/Research Agent.json | 35 ++++- .../Research Translation Loop.json | 30 +++- .../SEO Keyword Generator.json | 15 +- .../starter_projects/SaaS Pricing.json | 15 +- .../starter_projects/Search agent.json | 15 +- .../Sequential Tasks Agents.json | 40 ++++- .../starter_projects/Simple Agent.json | 20 ++- .../starter_projects/Social Media Agent.json | 20 ++- .../Text Sentiment Analysis.json | 15 +- .../Travel Planning Agents.json | 20 ++- .../Twitter Thread Generator.json | 45 ++++-- .../starter_projects/Vector Store RAG.json | 42 ++++-- .../starter_projects/Youtube Analysis.json | 25 ++- .../base/langflow/interface/components.py | 48 ++++-- .../tests/unit/custom/test_utils_metadata.py | 142 ++++++++++++++++++ src/backend/tests/unit/test_code_hash.py | 59 ++++++++ 35 files changed, 863 insertions(+), 165 deletions(-) create mode 100644 src/backend/tests/unit/custom/test_utils_metadata.py create mode 100644 src/backend/tests/unit/test_code_hash.py diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index 2d2b82305..9f78efe91 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -2,6 +2,7 @@ import ast import asyncio import contextlib +import hashlib import inspect import re import traceback @@ -32,6 +33,30 @@ from langflow.utils import validate from langflow.utils.util import get_base_classes +def _generate_code_hash(source_code: str, modname: str, class_name: str) -> str: + """Generate a hash of the component source code. + + Args: + source_code: The source code string + modname: The module name for context + class_name: The class name for context + + Returns: + SHA256 hash of the source code + + Raises: + ValueError: If source_code is empty or None + UnicodeEncodeError: If source_code cannot be encoded + TypeError: If source_code is not a string + """ + if not source_code: + msg = f"Empty source code for {class_name} in {modname}" + raise ValueError(msg) + + # Generate SHA256 hash of the source code + return hashlib.sha256(source_code.encode("utf-8")).hexdigest()[:12] # First 12 chars for brevity + + class UpdateBuildConfigError(Exception): pass @@ -415,7 +440,7 @@ def add_code_field_to_build_config(build_config: dict, raw_code: str): def build_custom_component_template_from_inputs( - custom_component: Component | CustomComponent, user_id: str | UUID | None = None + custom_component: Component | CustomComponent, user_id: str | UUID | None = None, module_name: str | None = None ): # The List of Inputs fills the role of the build_config and the entrypoint_args """Builds a frontend node template from a custom component using its input-based configuration. @@ -452,6 +477,13 @@ def build_custom_component_template_from_inputs( # ! This should be removed when we have a better way to handle this frontend_node.set_base_classes_from_outputs() reorder_fields(frontend_node, cc_instance._get_field_order()) + if module_name: + frontend_node.metadata["module"] = module_name + + # Generate code hash for cache invalidation and debugging + code_hash = _generate_code_hash(custom_component._code, module_name, ctype_name) + if code_hash: + frontend_node.metadata["code_hash"] = code_hash return frontend_node.to_dict(keep_name=False), cc_instance @@ -459,6 +491,7 @@ def build_custom_component_template_from_inputs( def build_custom_component_template( custom_component: CustomComponent, user_id: str | UUID | None = None, + module_name: str | None = None, ) -> tuple[dict[str, Any], CustomComponent | Component]: """Builds a frontend node template and instance for a custom component. @@ -490,7 +523,9 @@ def build_custom_component_template( ) try: if "inputs" in custom_component.template_config: - return build_custom_component_template_from_inputs(custom_component, user_id=user_id) + return build_custom_component_template_from_inputs( + custom_component, user_id=user_id, module_name=module_name + ) frontend_node = CustomComponentFrontendNode(**custom_component.template_config) field_config, custom_instance = run_build_config( @@ -509,6 +544,14 @@ def build_custom_component_template( reorder_fields(frontend_node, custom_instance._get_field_order()) + if module_name: + frontend_node.metadata["module"] = module_name + + # Generate code hash for cache invalidation and debugging + code_hash = _generate_code_hash(custom_component._code, module_name, custom_component.__class__.__name__) + if code_hash: + frontend_node.metadata["code_hash"] = code_hash + return frontend_node.to_dict(keep_name=False), custom_instance except Exception as exc: if isinstance(exc, HTTPException): @@ -525,6 +568,7 @@ def build_custom_component_template( def create_component_template( component: dict | None = None, component_extractor: Component | CustomComponent | None = None, + module_name: str | None = None, ): """Creates a component template and instance from either a component dictionary or an existing component extractor. @@ -539,7 +583,9 @@ def create_component_template( component_extractor = Component(_code=component_code) - component_template, component_instance = build_custom_component_template(component_extractor) + component_template, component_instance = build_custom_component_template( + component_extractor, module_name=module_name + ) if not component_template["output_types"] and component_output_types: component_template["output_types"] = component_output_types diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json index 667f3852b..1b0332889 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json @@ -361,7 +361,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.5.0", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -659,7 +662,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.5.0", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json index b41bba854..6d9ab9bad 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json @@ -116,7 +116,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -402,7 +405,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -608,7 +614,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json index a17333fd3..d0b1a5d36 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json @@ -176,7 +176,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -348,7 +351,10 @@ "icon": "type", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -461,7 +467,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -781,7 +790,10 @@ "key": "ParserComponent", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "556209520650", + "module": "langflow.components.processing.parser.ParserComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -965,7 +977,10 @@ "key": "URLComponent", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "a81817a7f244", + "module": "langflow.components.data.url.URLComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json index 7d4a06b4f..099ca17d2 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json @@ -236,7 +236,10 @@ "icon": "message-square-more", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "5ca89b168f3f", + "module": "langflow.components.helpers.memory.MemoryComponent" + }, "output_types": [], "outputs": [ { @@ -559,7 +562,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -1918,7 +1924,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -2232,7 +2241,10 @@ "icon": "MessagesSquare", "key": "ChatOutput", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json index 596178185..4ed0b8ea0 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json @@ -146,7 +146,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -438,7 +441,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -778,7 +784,10 @@ "is_output": null, "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "name": "", "output_types": [], "outputs": [ @@ -1226,7 +1235,10 @@ "icon": "file-text", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "0c57c835f136", + "module": "langflow.components.data.file.FileComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json index e4c928789..9ad620417 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json @@ -149,7 +149,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -461,7 +464,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1286,7 +1292,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "ad2a6f4552c0", + "module": "langflow.components.processing.structured_output.StructuredOutputComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json index 0b931edee..d51b5f641 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json @@ -204,7 +204,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -511,7 +514,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "556209520650", + "module": "langflow.components.processing.parser.ParserComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -690,7 +696,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -992,7 +1001,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "556209520650", + "module": "langflow.components.processing.parser.ParserComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1185,7 +1197,10 @@ "icon": "AstraDB", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "38a337e89ff4", + "module": "langflow.components.vectorstores.astradb.AstraDBVectorStoreComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -2566,7 +2581,10 @@ "frozen": false, "icon": "braces", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "ad2a6f4552c0", + "module": "langflow.components.processing.structured_output.StructuredOutputComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json index 67bd79431..1c7fe85df 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json @@ -233,7 +233,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -538,7 +541,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -871,7 +877,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -999,7 +1008,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "ad2a6f4552c0", + "module": "langflow.components.processing.structured_output.StructuredOutputComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json index 1f85a308b..44e95c8b4 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json @@ -290,7 +290,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.1.1", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -585,7 +588,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -756,7 +762,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -868,7 +877,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -1025,7 +1037,10 @@ "frozen": false, "icon": "MessagesSquare", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -1326,7 +1341,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -1541,7 +1559,10 @@ "frozen": false, "icon": "TavilyIcon", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6843645056d9", + "module": "langflow.components.tavily.tavily_search.TavilySearchComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json b/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json index c0978a30a..ba78f7d2a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json @@ -170,7 +170,10 @@ "is_input": null, "is_output": null, "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "minimized": false, "name": "", "output_types": [], @@ -301,7 +304,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -662,7 +668,10 @@ "icon": "Needle", "key": "needle", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "57d868cb067b", + "module": "langflow.components.needle.needle.NeedleComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -867,7 +876,10 @@ "icon": "MessagesSquare", "key": "ChatInput", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json index 5f262b8d7..dd85aa562 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json @@ -195,7 +195,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -493,7 +496,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -832,7 +838,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "ad2a6f4552c0", + "module": "langflow.components.processing.structured_output.StructuredOutputComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1180,7 +1189,10 @@ "icon": "TavilyIcon", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "6843645056d9", + "module": "langflow.components.tavily.tavily_search.TavilySearchComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json index f076025a7..e26c51996 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json @@ -313,7 +313,10 @@ "icon": "AssemblyAI", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "6fd1a65a4904", + "module": "langflow.components.assemblyai.assemblyai_poll_transcript.AssemblyAITranscriptionJobPoller" + }, "minimized": false, "output_types": [], "outputs": [ @@ -466,7 +469,10 @@ "is_output": null, "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "minimized": false, "name": "", "output_types": [], @@ -619,7 +625,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -921,7 +930,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.1.1", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1223,7 +1235,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1526,7 +1541,10 @@ "is_output": null, "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "minimized": false, "name": "", "output_types": [], @@ -1699,7 +1717,10 @@ "icon": "message-square-more", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "5ca89b168f3f", + "module": "langflow.components.helpers.memory.MemoryComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -2026,7 +2047,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -2441,7 +2465,10 @@ "icon": "AssemblyAI", "key": "AssemblyAITranscriptionJobCreator", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "03525d13fcc0", + "module": "langflow.components.assemblyai.assemblyai_start_transcript.AssemblyAITranscriptionJobCreator" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json index a43d3ca1f..309107af7 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json @@ -147,7 +147,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -453,7 +456,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -791,7 +797,10 @@ "is_output": null, "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "name": "", "output_types": [], "outputs": [ @@ -949,7 +958,10 @@ "key": "Memory", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "5ca89b168f3f", + "module": "langflow.components.helpers.memory.MemoryComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json index f7469d94e..a38b2eac0 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json @@ -204,7 +204,10 @@ "icon": "AgentQL", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "ce845cc47ae8", + "module": "langflow.components.agentql.agentql_api.AgentQL" + }, "minimized": false, "output_types": [], "outputs": [ @@ -557,7 +560,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -896,7 +902,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1198,7 +1207,10 @@ "icon": "save", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f244023207e", + "module": "langflow.components.processing.save_file.SaveToFileComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json b/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json index c0b54a124..9e3056f9e 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json @@ -231,7 +231,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -544,7 +547,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1882,7 +1888,10 @@ "icon": "binary", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "93faf11517da", + "module": "langflow.components.models.embedding_model.EmbeddingModelComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -2172,7 +2181,10 @@ "icon": "FAISS", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "ed38680af3a6", + "module": "langflow.components.vectorstores.faiss.FaissVectorStoreComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -2505,7 +2517,10 @@ "key": "MCPTools", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "d134bdfe1fc3", + "module": "langflow.components.agents.mcp_component.MCPToolsComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json index 9a732bc52..5fd9fa6b5 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json @@ -111,7 +111,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -425,7 +428,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -824,7 +830,10 @@ "icon": "Globe", "key": "APIRequest", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "a648ad26f226", + "module": "langflow.components.data.api_request.APIRequestComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json index 8368bb6b5..d956356fe 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json @@ -191,7 +191,10 @@ "key": "TextInput", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -307,7 +310,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -759,7 +765,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "ad2a6f4552c0", + "module": "langflow.components.processing.structured_output.StructuredOutputComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json index ad346396b..3d793bbfb 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json @@ -136,7 +136,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.3.2", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -449,7 +452,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.3.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -760,7 +766,10 @@ "icon": "TavilyIcon", "legacy": false, "lf_version": "1.3.2", - "metadata": {}, + "metadata": { + "code_hash": "6843645056d9", + "module": "langflow.components.tavily.tavily_search.TavilySearchComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1158,7 +1167,10 @@ "icon": "AgentQL", "legacy": false, "lf_version": "1.3.2", - "metadata": {}, + "metadata": { + "code_hash": "ce845cc47ae8", + "module": "langflow.components.agentql.agentql_api.AgentQL" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json index 5d876d6aa..c19c3992c 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json @@ -315,7 +315,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -473,7 +476,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -768,7 +774,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -980,7 +989,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -1109,7 +1121,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -1242,7 +1257,10 @@ "icon": "TavilyIcon", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6843645056d9", + "module": "langflow.components.tavily.tavily_search.TavilySearchComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1640,7 +1658,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json index cecbb901b..235dda0ec 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json @@ -227,7 +227,10 @@ "icon": "arXiv", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "b61405ff011f", + "module": "langflow.components.arxiv.arxiv.ArXivComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -385,7 +388,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -693,7 +699,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1027,7 +1036,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "556209520650", + "module": "langflow.components.processing.parser.ParserComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1199,7 +1211,10 @@ "icon": "infinity", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "5b234f78c942", + "module": "langflow.components.logic.loop.LoopComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1609,7 +1624,10 @@ "icon": "repeat", "key": "TypeConverterComponent", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "38e56a852063", + "module": "langflow.components.processing.converter.TypeConverterComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json index 64d35ff64..3e4232e42 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json @@ -118,7 +118,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -420,7 +423,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -555,7 +561,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json index 29896d91a..ee3ed3b89 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json @@ -115,7 +115,10 @@ "icon": "braces", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "output_types": [], "outputs": [ { @@ -366,7 +369,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -708,7 +714,10 @@ "key": "CalculatorComponent", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "3139fe9e04a5", + "module": "langflow.components.helpers.calculator_core.CalculatorComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json index 367eefcef..f4bdf5926 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json @@ -102,7 +102,10 @@ "icon": "ScrapeGraph", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "cdea312e9de9", + "module": "langflow.components.scrapegraph.scrapegraph_search_api.ScrapeGraphSearchApi" + }, "minimized": false, "output_types": [], "outputs": [ @@ -273,7 +276,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -584,7 +590,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json index 6573eebb8..e9a866a75 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json @@ -1447,7 +1447,10 @@ "is_output": null, "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "name": "", "output_types": [], "outputs": [ @@ -1582,7 +1585,10 @@ "is_output": null, "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "name": "", "output_types": [], "outputs": [ @@ -1720,7 +1726,10 @@ "is_output": null, "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "name": "", "output_types": [], "outputs": [ @@ -1900,7 +1909,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -2787,7 +2799,10 @@ "frozen": false, "icon": "trending-up", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "e17c98e16912", + "module": "langflow.components.yahoosearch.yahoo.YfinanceComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -2998,7 +3013,10 @@ "icon": "calculator", "key": "CalculatorComponent", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "3139fe9e04a5", + "module": "langflow.components.helpers.calculator_core.CalculatorComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -3152,7 +3170,10 @@ "frozen": false, "icon": "TavilyIcon", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6843645056d9", + "module": "langflow.components.tavily.tavily_search.TavilySearchComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -3549,7 +3570,10 @@ "icon": "MessagesSquare", "key": "ChatOutput", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json index d3816da2a..be2b756fd 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json @@ -190,7 +190,10 @@ "key": "CalculatorComponent", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "3139fe9e04a5", + "module": "langflow.components.helpers.calculator_core.CalculatorComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -345,7 +348,10 @@ "icon": "MessagesSquare", "key": "ChatInput", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -660,7 +666,10 @@ "icon": "MessagesSquare", "key": "ChatOutput", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1515,7 +1524,10 @@ "icon": "layout-template", "key": "URLComponent", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "a81817a7f244", + "module": "langflow.components.data.url.URLComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json index 770d439f3..17a04cb24 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json @@ -143,7 +143,10 @@ "icon": "Apify", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "233d7ef687d5", + "module": "langflow.components.apify.apify_actor.ApifyActorsComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -346,7 +349,10 @@ "icon": "Apify", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "233d7ef687d5", + "module": "langflow.components.apify.apify_actor.ApifyActorsComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -636,7 +642,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -948,7 +957,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.4.2", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json index 80e202dec..4da098c2d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json @@ -712,7 +712,10 @@ "frozen": false, "icon": "MessagesSquare", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1020,7 +1023,10 @@ "frozen": false, "icon": "MessagesSquare", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -2294,7 +2300,10 @@ "frozen": false, "icon": "file-text", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "0c57c835f136", + "module": "langflow.components.data.file.FileComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json index 21793b5d6..cbc4eb00d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json @@ -227,7 +227,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -525,7 +528,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -1269,7 +1275,10 @@ "key": "CalculatorComponent", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "3139fe9e04a5", + "module": "langflow.components.helpers.calculator_core.CalculatorComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1424,7 +1433,10 @@ "icon": "SearchAPI", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "727befdc79e7", + "module": "langflow.components.searchapi.search.SearchComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json index 7147ccbc1..d96cc7871 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json @@ -282,7 +282,10 @@ "frozen": false, "icon": "MessagesSquare", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -591,7 +594,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -706,7 +712,10 @@ "frozen": false, "icon": "MessagesSquare", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1012,7 +1021,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -1117,7 +1129,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -1222,7 +1237,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -1327,7 +1345,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -1432,7 +1453,10 @@ "icon": "type", "legacy": false, "lf_version": "1.0.19.post2", - "metadata": {}, + "metadata": { + "code_hash": "efdcba3771af", + "module": "langflow.components.input_output.text.TextInputComponent" + }, "output_types": [], "outputs": [ { @@ -1585,7 +1609,10 @@ "frozen": false, "icon": "braces", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "minimized": false, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json index 44f22d760..ca0034e4b 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json @@ -319,7 +319,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "output_types": [], "outputs": [ { @@ -612,7 +615,10 @@ "is_output": null, "legacy": false, "lf_version": "1.1.1", - "metadata": {}, + "metadata": { + "code_hash": "3bf0b511e227", + "module": "langflow.components.prompts.prompt.PromptComponent" + }, "name": "", "output_types": [], "outputs": [ @@ -787,7 +793,10 @@ "icon": "scissors-line-dashed", "legacy": false, "lf_version": "1.1.1", - "metadata": {}, + "metadata": { + "code_hash": "dbf2e9d2319d", + "module": "langflow.components.processing.split_text.SplitTextComponent" + }, "output_types": [], "outputs": [ { @@ -1073,7 +1082,10 @@ "icon": "MessagesSquare", "legacy": false, "lf_version": "1.1.1", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "output_types": [], "outputs": [ { @@ -1387,7 +1399,10 @@ "icon": "OpenAI", "legacy": false, "lf_version": "1.2.0", - "metadata": {}, + "metadata": { + "code_hash": "2691dee277c9", + "module": "langflow.components.openai.openai.OpenAIEmbeddingsComponent" + }, "output_types": [], "outputs": [ { @@ -1920,7 +1935,10 @@ "icon": "OpenAI", "legacy": false, "lf_version": "1.1.1", - "metadata": {}, + "metadata": { + "code_hash": "2691dee277c9", + "module": "langflow.components.openai.openai.OpenAIEmbeddingsComponent" + }, "output_types": [], "outputs": [ { @@ -2690,7 +2708,10 @@ "frozen": false, "icon": "AstraDB", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "38a337e89ff4", + "module": "langflow.components.vectorstores.astradb.AstraDBVectorStoreComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -3463,7 +3484,10 @@ "frozen": false, "icon": "AstraDB", "legacy": false, - "metadata": {}, + "metadata": { + "code_hash": "38a337e89ff4", + "module": "langflow.components.vectorstores.astradb.AstraDBVectorStoreComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -4786,4 +4810,4 @@ "rag", "q-a" ] -} +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index c7d00ea68..635bca7fb 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -284,7 +284,10 @@ "key": "BatchRunComponent", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "86f4b70ee039", + "module": "langflow.components.processing.batch_run.BatchRunComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -499,7 +502,10 @@ "key": "YouTubeCommentsComponent", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "aeda2975f4aa", + "module": "langflow.components.youtube.comments.YouTubeCommentsComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1432,7 +1438,10 @@ "key": "ChatOutput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "6f74e04e39d5", + "module": "langflow.components.input_output.chat_output.ChatOutput" + }, "minimized": true, "output_types": [], "outputs": [ @@ -1740,7 +1749,10 @@ "last_updated": "2025-07-07T14:52:15.000Z", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "c9f0262ff0b6", + "module": "langflow.components.youtube.youtube_transcripts.YouTubeTranscriptsComponent" + }, "minimized": false, "output_types": [], "outputs": [ @@ -2487,7 +2499,10 @@ "key": "ChatInput", "legacy": false, "lf_version": "1.4.3", - "metadata": {}, + "metadata": { + "code_hash": "192913db3453", + "module": "langflow.components.input_output.chat.ChatInput" + }, "minimized": true, "output_types": [], "outputs": [ diff --git a/src/backend/base/langflow/interface/components.py b/src/backend/base/langflow/interface/components.py index 0b5cda0cc..49b086b6c 100644 --- a/src/backend/base/langflow/interface/components.py +++ b/src/backend/base/langflow/interface/components.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any from loguru import logger -from langflow.custom.utils import abuild_custom_components, create_component_template +from langflow.custom.utils import abuild_custom_components, create_component_template, get_all_types_dict from langflow.services.settings.base import BASE_COMPONENTS_PATH if TYPE_CHECKING: @@ -133,7 +133,11 @@ def _process_single_module(modname: str) -> tuple[str, dict] | None: try: comp_instance = obj() - comp_template, _ = create_component_template(component_extractor=comp_instance) + # modname is the full module name without the name of the obj + full_module_name = f"{modname}.{name}" + comp_template, _ = create_component_template( + component_extractor=comp_instance, module_name=full_module_name + ) component_name = obj.name if hasattr(obj, "name") and obj.name else name module_components[component_name] = comp_template except Exception as e: # noqa: BLE001 @@ -149,6 +153,29 @@ def _process_single_module(modname: str) -> tuple[str, dict] | None: return (top_level, module_components) +async def _determine_loading_strategy(settings_service: SettingsService) -> dict: + """Determines and executes the appropriate component loading strategy. + + Args: + settings_service: Service providing access to application settings + + Returns: + Dictionary containing loaded component types and templates + """ + if settings_service.settings.lazy_load_components: + # Partial loading mode - just load component metadata + logger.debug("Using partial component loading") + return await aget_component_metadata(settings_service.settings.components_path) + if ( + settings_service.settings.components_path + and BASE_COMPONENTS_PATH not in settings_service.settings.components_path + ): + # Traditional full loading + return await get_all_types_dict(settings_service.settings.components_path) + # No custom components to load + return {} + + async def get_and_cache_all_types_dict( settings_service: SettingsService, ): @@ -163,28 +190,17 @@ async def get_and_cache_all_types_dict( logger.debug("Building components cache") langflow_components = await import_langflow_components() - component_cache.all_types_dict = {} - if settings_service.settings.lazy_load_components: - # Partial loading mode - just load component metadata - logger.debug("Using partial component loading") - component_cache.all_types_dict = await aget_component_metadata(settings_service.settings.components_path) - elif ( - settings_service.settings.components_path - and BASE_COMPONENTS_PATH not in settings_service.settings.components_path - ): - # Traditional full loading - component_cache.all_types_dict = await aget_all_types_dict(settings_service.settings.components_path) + custom_components_dict = await _determine_loading_strategy(settings_service) # Log custom component loading stats - components_dict = component_cache.all_types_dict or {} - component_count = sum(len(comps) for comps in components_dict.get("components", {}).values()) + component_count = sum(len(comps) for comps in custom_components_dict.values()) if component_count > 0 and settings_service.settings.components_path: logger.debug(f"Built {component_count} custom components from {settings_service.settings.components_path}") # merge the dicts component_cache.all_types_dict = { **langflow_components["components"], - **components_dict, + **custom_components_dict, } component_count = sum(len(comps) for comps in component_cache.all_types_dict.values()) logger.debug(f"Loaded {component_count} components") diff --git a/src/backend/tests/unit/custom/test_utils_metadata.py b/src/backend/tests/unit/custom/test_utils_metadata.py new file mode 100644 index 000000000..58772a7d1 --- /dev/null +++ b/src/backend/tests/unit/custom/test_utils_metadata.py @@ -0,0 +1,142 @@ +"""Test metadata functionality in custom utils.""" + +from unittest.mock import Mock, patch + +import pytest +from langflow.custom.utils import _generate_code_hash + + +class TestCodeHashGeneration: + """Test the _generate_code_hash function.""" + + def test_hash_generation_basic(self): + """Test basic hash generation.""" + source = "def test(): pass" + modname = "test_module" + class_name = "TestClass" + + result = _generate_code_hash(source, modname, class_name) + + assert isinstance(result, str) + assert len(result) == 12 + assert all(c in "0123456789abcdef" for c in result) + + def test_hash_empty_source_raises(self): + """Test that empty source raises ValueError.""" + with pytest.raises(ValueError, match="Empty source code"): + _generate_code_hash("", "mod", "cls") + + def test_hash_none_source_raises(self): + """Test that None source raises ValueError.""" + with pytest.raises(ValueError, match="Empty source code"): + _generate_code_hash(None, "mod", "cls") + + def test_hash_consistency(self): + """Test that same code produces same hash.""" + source = "class A: pass" + hash1 = _generate_code_hash(source, "mod", "A") + hash2 = _generate_code_hash(source, "mod", "A") + assert hash1 == hash2 + + def test_hash_different_code(self): + """Test that different code produces different hash.""" + hash1 = _generate_code_hash("class A: pass", "mod", "A") + hash2 = _generate_code_hash("class B: pass", "mod", "B") + assert hash1 != hash2 + + +class TestMetadataInTemplateBuilders: + """Test metadata addition in template building functions.""" + + @patch("langflow.custom.utils.ComponentFrontendNode") + def test_build_from_inputs_adds_metadata_with_module(self, mock_frontend_class): + """Test that build_custom_component_template_from_inputs adds metadata when module_name is provided.""" + from langflow.custom.custom_component.component import Component + from langflow.custom.utils import build_custom_component_template_from_inputs + + # Setup mock frontend node + mock_frontend = Mock() + mock_frontend.metadata = {} + mock_frontend.outputs = [] + mock_frontend.to_dict = Mock(return_value={"test": "data"}) + mock_frontend.validate_component = Mock() + mock_frontend.set_base_classes_from_outputs = Mock() + mock_frontend_class.from_inputs.return_value = mock_frontend + + # Create test component + test_component = Mock(spec=Component) + test_component.__class__.__name__ = "TestComponent" + test_component._code = "class TestComponent: pass" + test_component.template_config = {"inputs": []} + + # Mock get_component_instance to return a mock instance + with patch("langflow.custom.utils.get_component_instance") as mock_get_instance: + mock_instance = Mock() + mock_instance.get_template_config = Mock(return_value={}) + mock_instance._get_field_order = Mock(return_value=[]) + mock_get_instance.return_value = mock_instance + + # Mock add_code_field to return the frontend node + with ( + patch("langflow.custom.utils.add_code_field", return_value=mock_frontend), + patch("langflow.custom.utils.reorder_fields"), + ): + # Call the function + template, _ = build_custom_component_template_from_inputs(test_component, module_name="test.module") + + # Verify metadata was added + assert "module" in mock_frontend.metadata + assert mock_frontend.metadata["module"] == "test.module" + assert "code_hash" in mock_frontend.metadata + assert len(mock_frontend.metadata["code_hash"]) == 12 + + @patch("langflow.custom.utils.CustomComponentFrontendNode") + def test_build_template_adds_metadata_with_module(self, mock_frontend_class): + """Test that build_custom_component_template adds metadata when module_name is provided.""" + from langflow.custom.custom_component.custom_component import CustomComponent + from langflow.custom.utils import build_custom_component_template + + # Setup mock frontend node + mock_frontend = Mock() + mock_frontend.metadata = {} + mock_frontend.to_dict = Mock(return_value={"test": "data"}) + mock_frontend_class.return_value = mock_frontend + + # Create test component + test_component = Mock(spec=CustomComponent) + test_component.__class__.__name__ = "CustomTestComponent" + test_component._code = "class CustomTestComponent: pass" + test_component.template_config = {"display_name": "Test"} + test_component.get_function_entrypoint_args = [] + test_component._get_function_entrypoint_return_type = [] + + # Mock helper functions + with patch("langflow.custom.utils.run_build_config") as mock_run_build: + mock_instance = Mock() + mock_instance._get_field_order = Mock(return_value=[]) + mock_run_build.return_value = ({}, mock_instance) + + with ( + patch("langflow.custom.utils.add_extra_fields"), + patch("langflow.custom.utils.add_code_field", return_value=mock_frontend), + patch("langflow.custom.utils.add_base_classes"), + patch("langflow.custom.utils.add_output_types"), + patch("langflow.custom.utils.reorder_fields"), + ): + # Call the function + template, _ = build_custom_component_template(test_component, module_name="custom.test") + + # Verify metadata was added + assert "module" in mock_frontend.metadata + assert mock_frontend.metadata["module"] == "custom.test" + assert "code_hash" in mock_frontend.metadata + assert len(mock_frontend.metadata["code_hash"]) == 12 + + def test_hash_generation_unicode(self): + """Test hash generation with unicode characters.""" + source = "# Test with unicode: 你好 🌟\nclass Component: pass" + result = _generate_code_hash(source, "unicode_mod", "Component") + + assert isinstance(result, str) + assert len(result) == 12 + assert all(c in "0123456789abcdef" for c in result) diff --git a/src/backend/tests/unit/test_code_hash.py b/src/backend/tests/unit/test_code_hash.py new file mode 100644 index 000000000..cfbc5fdc0 --- /dev/null +++ b/src/backend/tests/unit/test_code_hash.py @@ -0,0 +1,59 @@ +"""Test code hash and module metadata functionality.""" + +import pytest +from langflow.interface.components import import_langflow_components + + +@pytest.mark.asyncio +async def test_component_metadata_has_code_hash(): + """Test that built-in components have valid module and code_hash metadata.""" + result = await import_langflow_components() + assert result is not None + assert "components" in result + assert len(result["components"]) > 0 + + # Find first component to test + sample_category = None + sample_component = None + for category, components in result["components"].items(): + if components: + sample_category = category + sample_component = next(iter(components.values())) + break + assert sample_component is not None, "No components found to test" + + # Test metadata presence - metadata should be in the 'metadata' sub-field + assert "metadata" in sample_component, f"Metadata field missing from component in {sample_category}" + metadata = sample_component["metadata"] + + assert "module" in metadata, f"Module metadata missing from component in {sample_category}" + assert "code_hash" in metadata, f"Code hash metadata missing from component in {sample_category}" + + # Test that values are valid + module_name = metadata["module"] + code_hash = metadata["code_hash"] + assert isinstance(module_name, str), f"Invalid module name type: {type(module_name)}" + assert module_name, f"Invalid module name: {module_name}" + assert isinstance(code_hash, str), f"Invalid code hash type: {type(code_hash)}" + assert len(code_hash) == 12, f"Invalid code hash: {code_hash} (should be 12 chars)" + + +@pytest.mark.asyncio +async def test_code_hash_uniqueness(): + """Test that different built-in components have different code hashes.""" + result = await import_langflow_components() + all_hashes = [] + for components in result["components"].values(): + for comp in components.values(): + metadata = comp.get("metadata", {}) + if metadata.get("code_hash"): + all_hashes.append(metadata["code_hash"]) + + # Check that we have some components with metadata + assert len(all_hashes) > 0, "No components with code hashes found" + # Check that we have reasonable uniqueness in hashes + unique_hashes = len(set(all_hashes)) + total_hashes = len(all_hashes) + uniqueness_ratio = unique_hashes / total_hashes + # Should have high uniqueness (most components have different code) + assert uniqueness_ratio > 0.95, f"Hash uniqueness too low: {uniqueness_ratio:.1%}"