From fb29b248be1c1b2198f4909e69bf3bb53ca9acf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Tue, 2 Jul 2024 21:16:12 +0200 Subject: [PATCH] fix: run_flow_from_json circular dependency (#2485) * fix: run_flow_from_json circular dependency * fix: run_flow_from_json circular dependency * fix: run_flow_from_json circular dependency * fix mypy * fix mypy * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/backend/base/langflow/api/v1/base.py | 113 ----------------- .../base/langflow/base/prompts/api_utils.py | 118 +++++++++++++++++- 2 files changed, 115 insertions(+), 116 deletions(-) diff --git a/src/backend/base/langflow/api/v1/base.py b/src/backend/base/langflow/api/v1/base.py index b040d51c4..879d684cd 100644 --- a/src/backend/base/langflow/api/v1/base.py +++ b/src/backend/base/langflow/api/v1/base.py @@ -50,116 +50,3 @@ class PromptValidationResponse(BaseModel): input_variables: list # object return for tweak call frontend_node: Optional[FrontendNodeRequest] = None - - -INVALID_CHARACTERS = { - " ", - ",", - ".", - ":", - ";", - "!", - "?", - "/", - "\\", - "(", - ")", - "[", - "]", -} - -INVALID_NAMES = { - "input_variables", - "output_parser", - "partial_variables", - "template", - "template_format", - "validate_template", -} - - -def is_json_like(var): - if var.startswith("{{") and var.endswith("}}"): - # If it is a double brance variable - # we don't want to validate any of its content - return True - # the above doesn't work on all cases because the json string can be multiline - # or indented which can add \n or spaces at the start or end of the string - # test_case_3 new_var == '\n{{\n "test": "hello",\n "text": "world"\n}}\n' - # what we can do is to remove the \n and spaces from the start and end of the string - # and then check if the string starts with {{ and ends with }} - var = var.strip() - var = var.replace("\n", "") - var = var.replace(" ", "") - # Now it should be a valid json string - return var.startswith("{{") and var.endswith("}}") - - -def fix_variable(var, invalid_chars, wrong_variables): - if not var: - return var, invalid_chars, wrong_variables - new_var = var - - # Handle variables starting with a number - if var[0].isdigit(): - invalid_chars.append(var[0]) - new_var, invalid_chars, wrong_variables = fix_variable(var[1:], invalid_chars, wrong_variables) - - # Temporarily replace {{ and }} to avoid treating them as invalid - new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ") - - # Remove invalid characters - for char in new_var: - if char in INVALID_CHARACTERS: - invalid_chars.append(char) - new_var = new_var.replace(char, "") - if var not in wrong_variables: # Avoid duplicating entries - wrong_variables.append(var) - - # Restore {{ and }} - new_var = new_var.replace("ᴛᴇᴍᴘᴏᴘᴇɴ", "{{").replace("ᴛᴇᴍᴘᴄʟᴏsᴇ", "}}") - - return new_var, invalid_chars, wrong_variables - - -def check_variable(var, invalid_chars, wrong_variables, empty_variables): - if any(char in invalid_chars for char in var): - wrong_variables.append(var) - elif var == "": - empty_variables.append(var) - return wrong_variables, empty_variables - - -def check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables): - if any(var for var in input_variables if var not in fixed_variables): - error_message = ( - f"Error: Input variables contain invalid characters or formats. \n" - f"Invalid variables: {', '.join(wrong_variables)}.\n" - f"Empty variables: {', '.join(empty_variables)}. \n" - f"Fixed variables: {', '.join(fixed_variables)}." - ) - raise ValueError(error_message) - - -def check_input_variables(input_variables): - invalid_chars = [] - fixed_variables = [] - wrong_variables = [] - empty_variables = [] - variables_to_check = [] - - for var in input_variables: - # First, let's check if the variable is a JSON string - # because if it is, it won't be considered a variable - # and we don't need to validate it - if is_json_like(var): - continue - - new_var, wrong_variables, empty_variables = fix_variable(var, invalid_chars, wrong_variables) - wrong_variables, empty_variables = check_variable(var, INVALID_CHARACTERS, wrong_variables, empty_variables) - fixed_variables.append(new_var) - variables_to_check.append(var) - - check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables) - - return fixed_variables diff --git a/src/backend/base/langflow/base/prompts/api_utils.py b/src/backend/base/langflow/base/prompts/api_utils.py index c8ba4de2a..29516241a 100644 --- a/src/backend/base/langflow/base/prompts/api_utils.py +++ b/src/backend/base/langflow/base/prompts/api_utils.py @@ -5,17 +5,129 @@ from fastapi import HTTPException from langchain_core.prompts import PromptTemplate from loguru import logger -from langflow.api.v1.base import INVALID_NAMES, check_input_variables from langflow.interface.utils import extract_input_variables_from_prompt from langflow.template.field.prompt import DefaultPromptField +_INVALID_CHARACTERS = { + " ", + ",", + ".", + ":", + ";", + "!", + "?", + "/", + "\\", + "(", + ")", + "[", + "]", +} + +_INVALID_NAMES = { + "input_variables", + "output_parser", + "partial_variables", + "template", + "template_format", + "validate_template", +} + + +def _is_json_like(var): + if var.startswith("{{") and var.endswith("}}"): + # If it is a double brance variable + # we don't want to validate any of its content + return True + # the above doesn't work on all cases because the json string can be multiline + # or indented which can add \n or spaces at the start or end of the string + # test_case_3 new_var == '\n{{\n "test": "hello",\n "text": "world"\n}}\n' + # what we can do is to remove the \n and spaces from the start and end of the string + # and then check if the string starts with {{ and ends with }} + var = var.strip() + var = var.replace("\n", "") + var = var.replace(" ", "") + # Now it should be a valid json string + return var.startswith("{{") and var.endswith("}}") + + +def _fix_variable(var, invalid_chars, wrong_variables): + if not var: + return var, invalid_chars, wrong_variables + new_var = var + + # Handle variables starting with a number + if var[0].isdigit(): + invalid_chars.append(var[0]) + new_var, invalid_chars, wrong_variables = _fix_variable(var[1:], invalid_chars, wrong_variables) + + # Temporarily replace {{ and }} to avoid treating them as invalid + new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ") + + # Remove invalid characters + for char in new_var: + if char in _INVALID_CHARACTERS: + invalid_chars.append(char) + new_var = new_var.replace(char, "") + if var not in wrong_variables: # Avoid duplicating entries + wrong_variables.append(var) + + # Restore {{ and }} + new_var = new_var.replace("ᴛᴇᴍᴘᴏᴘᴇɴ", "{{").replace("ᴛᴇᴍᴘᴄʟᴏsᴇ", "}}") + + return new_var, invalid_chars, wrong_variables + + +def _check_variable(var, invalid_chars, wrong_variables, empty_variables): + if any(char in invalid_chars for char in var): + wrong_variables.append(var) + elif var == "": + empty_variables.append(var) + return wrong_variables, empty_variables + + +def _check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables): + if any(var for var in input_variables if var not in fixed_variables): + error_message = ( + f"Error: Input variables contain invalid characters or formats. \n" + f"Invalid variables: {', '.join(wrong_variables)}.\n" + f"Empty variables: {', '.join(empty_variables)}. \n" + f"Fixed variables: {', '.join(fixed_variables)}." + ) + raise ValueError(error_message) + + +def _check_input_variables(input_variables): + invalid_chars = [] + fixed_variables = [] + wrong_variables = [] + empty_variables = [] + variables_to_check = [] + + for var in input_variables: + # First, let's check if the variable is a JSON string + # because if it is, it won't be considered a variable + # and we don't need to validate it + if _is_json_like(var): + continue + + new_var, wrong_variables, empty_variables = _fix_variable(var, invalid_chars, wrong_variables) + wrong_variables, empty_variables = _check_variable(var, _INVALID_CHARACTERS, wrong_variables, empty_variables) + fixed_variables.append(new_var) + variables_to_check.append(var) + + _check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables) + + return fixed_variables + + def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[str]: input_variables = extract_input_variables_from_prompt(prompt_template) # Check if there are invalid characters in the input_variables - input_variables = check_input_variables(input_variables) - if any(var in INVALID_NAMES for var in input_variables): + input_variables = _check_input_variables(input_variables) + if any(var in _INVALID_NAMES for var in input_variables): raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ") try: