diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d4eaafb95f..8be55150cc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ on: - "src/backend/**" env: - POETRY_VERSION: "1.7.0" + POETRY_VERSION: "1.8.2" jobs: lint: @@ -22,7 +22,6 @@ jobs: strategy: matrix: python-version: - - "3.9" - "3.10" - "3.11" steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10ab9b3244..629633f23d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ on: - "src/backend/**" env: - POETRY_VERSION: "1.5.0" + POETRY_VERSION: "1.8.2" jobs: build: diff --git a/deploy/base.Dockerfile b/deploy/base.Dockerfile index 323663283f..58fae3dab3 100644 --- a/deploy/base.Dockerfile +++ b/deploy/base.Dockerfile @@ -23,7 +23,7 @@ ENV PYTHONUNBUFFERED=1 \ \ # poetry # https://python-poetry.org/docs/configuration/#using-environment-variables - POETRY_VERSION=1.5.1 \ + POETRY_VERSION=1.8.2 \ # make poetry install to this location POETRY_HOME="/opt/poetry" \ # make poetry create the virtual environment in the project's root diff --git a/src/backend/base/langflow/__main__.py b/src/backend/base/langflow/__main__.py index 356a7e7cd4..c3fb5bd377 100644 --- a/src/backend/base/langflow/__main__.py +++ b/src/backend/base/langflow/__main__.py @@ -9,10 +9,7 @@ import httpx import typer from dotenv import load_dotenv -from multiprocess import ( - Process, # type: ignore - cpu_count, # type: ignore -) +from multiprocess import Process, cpu_count # type: ignore from rich import box from rich import print as rprint from rich.console import Console diff --git a/src/backend/base/langflow/alembic/helpers/flow.py b/src/backend/base/langflow/alembic/helpers/flow.py deleted file mode 100644 index 328e99ca16..0000000000 --- a/src/backend/base/langflow/alembic/helpers/flow.py +++ /dev/null @@ -1,199 +0,0 @@ -from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Union - -from pydantic.v1 import BaseModel, Field, create_model -from sqlmodel import select - -from langflow.schema.schema import INPUT_FIELD_NAME, Record -from langflow.services.database.models.flow.model import Flow -from langflow.services.deps import session_scope - -if TYPE_CHECKING: - from langflow.graph.graph.base import Graph - from langflow.graph.vertex.base import Vertex - -INPUT_TYPE_MAP = { - "ChatInput": {"type_hint": "Optional[str]", "default": '""'}, - "TextInput": {"type_hint": "Optional[str]", "default": '""'}, - "JSONInput": {"type_hint": "Optional[dict]", "default": "{}"}, -} - - -def list_flows(*, user_id: Optional[str] = None) -> List[Record]: - if not user_id: - raise ValueError("Session is invalid") - try: - with session_scope() as session: - flows = session.exec( - select(Flow).where(Flow.user_id == user_id).where(Flow.is_component == False) # noqa - ).all() - - flows_records = [flow.to_record() for flow in flows] - return flows_records - except Exception as e: - raise ValueError(f"Error listing flows: {e}") - - -async def load_flow( - user_id: str, flow_id: Optional[str] = None, flow_name: Optional[str] = None, tweaks: Optional[dict] = None -) -> "Graph": - from langflow.graph.graph.base import Graph - from langflow.processing.process import process_tweaks - - if not flow_id and not flow_name: - raise ValueError("Flow ID or Flow Name is required") - if not flow_id and flow_name: - flow_id = find_flow(flow_name, user_id) - if not flow_id: - raise ValueError(f"Flow {flow_name} not found") - - with session_scope() as session: - graph_data = flow.data if (flow := session.get(Flow, flow_id)) else None - if not graph_data: - raise ValueError(f"Flow {flow_id} not found") - if tweaks: - graph_data = process_tweaks(graph_data=graph_data, tweaks=tweaks) - graph = Graph.from_payload(graph_data, flow_id=flow_id) - return graph - - -def find_flow(flow_name: str, user_id: str) -> Optional[str]: - with session_scope() as session: - flow = session.exec(select(Flow).where(Flow.name == flow_name).where(Flow.user_id == user_id)).first() - return flow.id if flow else None - - -async def run_flow( - inputs: Union[dict, List[dict]] = None, - tweaks: Optional[dict] = None, - flow_id: Optional[str] = None, - flow_name: Optional[str] = None, - user_id: Optional[str] = None, -) -> Any: - graph = await load_flow(user_id, flow_id, flow_name, tweaks) - - if inputs is None: - inputs = [] - inputs_list = [] - inputs_components = [] - types = [] - for input_dict in inputs: - inputs_list.append({INPUT_FIELD_NAME: input_dict.get("input_value")}) - inputs_components.append(input_dict.get("components", [])) - types.append(input_dict.get("type", [])) - - return await graph.arun(inputs_list, inputs_components=inputs_components, types=types) - - -def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Coroutine: - """ - Generate a dynamic flow function based on the given inputs and flow ID. - - Args: - inputs (List[Vertex]): The list of input vertices for the flow. - flow_id (str): The ID of the flow. - - Returns: - Coroutine: The dynamic flow function. - - Raises: - None - - Example: - inputs = [vertex1, vertex2] - flow_id = "my_flow" - function = generate_function_for_flow(inputs, flow_id) - result = function(input1, input2) - """ - # Prepare function arguments with type hints and default values - args = [ - f"{input_.display_name.lower().replace(' ', '_')}: {INPUT_TYPE_MAP[input_.base_name]['type_hint']} = {INPUT_TYPE_MAP[input_.base_name]['default']}" - for input_ in inputs - ] - - # Maintain original argument names for constructing the tweaks dictionary - original_arg_names = [input_.display_name for input_ in inputs] - - # Prepare a Pythonic, valid function argument string - func_args = ", ".join(args) - - # Map original argument names to their corresponding Pythonic variable names in the function - arg_mappings = ", ".join( - f'"{original_name}": {name}' - for original_name, name in zip(original_arg_names, [arg.split(":")[0] for arg in args]) - ) - - func_body = f""" -from typing import Optional -async def flow_function({func_args}): - tweaks = {{ {arg_mappings} }} - from langflow.helpers.flow import run_flow - from langchain_core.tools import ToolException - try: - return await run_flow( - tweaks={{key: {{'input_value': value}} for key, value in tweaks.items()}}, - flow_id="{flow_id}", - ) - except Exception as e: - raise ToolException(f'Error running flow: ' + e) -""" - - compiled_func = compile(func_body, "", "exec") - local_scope = {} - exec(compiled_func, globals(), local_scope) - return local_scope["flow_function"] - - -def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, BaseModel]: - """ - Builds a dynamic function and schema for a given flow. - - Args: - flow_record (Record): The flow record containing information about the flow. - graph (Graph): The graph representing the flow. - - Returns: - Tuple[Callable, BaseModel]: A tuple containing the dynamic function and the schema. - """ - flow_id = flow_record.id - inputs = get_flow_inputs(graph) - dynamic_flow_function = generate_function_for_flow(inputs, flow_id) - schema = build_schema_from_inputs(flow_record.name, inputs) - return dynamic_flow_function, schema - - -def get_flow_inputs(graph: "Graph") -> List["Vertex"]: - """ - Retrieves the flow inputs from the given graph. - - Args: - graph (Graph): The graph object representing the flow. - - Returns: - List[Record]: A list of input records, where each record contains the ID, name, and description of the input vertex. - """ - inputs = [] - for vertex in graph.vertices: - if vertex.is_input: - inputs.append(vertex) - return inputs - - -def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> BaseModel: - """ - Builds a schema from the given inputs. - - Args: - name (str): The name of the schema. - inputs (List[tuple[str, str, str]]): A list of tuples representing the inputs. - Each tuple contains three elements: the input name, the input type, and the input description. - - Returns: - BaseModel: The schema model. - - """ - fields = {} - for input_ in inputs: - field_name = input_.display_name.lower().replace(" ", "_") - description = input_.description - fields[field_name] = (str, Field(default="", description=description)) - return create_model(name, **fields) diff --git a/src/backend/base/langflow/alembic/helpers/record.py b/src/backend/base/langflow/alembic/helpers/record.py deleted file mode 100644 index d7b1d2b060..0000000000 --- a/src/backend/base/langflow/alembic/helpers/record.py +++ /dev/null @@ -1,34 +0,0 @@ -from langchain_core.documents import Document - -from langflow.schema import Record - - -def docs_to_records(documents: list[Document]) -> list[Record]: - """ - Converts a list of Documents to a list of Records. - - Args: - documents (list[Document]): The list of Documents to convert. - - Returns: - list[Record]: The converted list of Records. - """ - return [Record.from_document(document) for document in documents] - - -def records_to_text(template: str, records: list[Record]) -> str: - """ - Converts a list of Records to a list of texts. - - Args: - records (list[Record]): The list of Records to convert. - - Returns: - list[str]: The converted list of texts. - """ - if isinstance(records, Record): - records = [records] - # Check if there are any format strings in the template - - formated_records = [template.format(data=record.data, **record.data) for record in records] - return "\n".join(formated_records) diff --git a/src/backend/base/langflow/api/utils.py b/src/backend/base/langflow/api/utils.py index c03a724a70..27576e9714 100644 --- a/src/backend/base/langflow/api/utils.py +++ b/src/backend/base/langflow/api/utils.py @@ -164,7 +164,7 @@ def get_is_component_from_data(data: dict): async def check_langflow_version(component: StoreComponentCreate): - from langflow import __version__ as current_version + from langflow.version.version import __version__ as current_version # type: ignore if not component.last_tested_version: component.last_tested_version = current_version diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index 7886321f73..16dcdca708 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -239,7 +239,7 @@ async def create_upload_file( # get endpoint to return version of langflow @router.get("/version") def get_version(): - from langflow.version import __version__ + from langflow.version import __version__ # type: ignore return {"version": __version__} diff --git a/src/backend/base/langflow/base/agents/agent.py b/src/backend/base/langflow/base/agents/agent.py index d0ce82d767..5ef498b665 100644 --- a/src/backend/base/langflow/base/agents/agent.py +++ b/src/backend/base/langflow/base/agents/agent.py @@ -1,10 +1,10 @@ -from typing import List, Union +from typing import List, Optional, Union, cast from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent from langchain_core.runnables import Runnable +from langflow.custom import CustomComponent from langflow.field_typing import BaseMemory, Text, Tool -from langflow.interface.custom.custom_component import CustomComponent class LCAgentComponent(CustomComponent): @@ -44,7 +44,7 @@ async def run_agent( inputs: str, input_variables: list[str], tools: List[Tool], - memory: BaseMemory = None, + memory: Optional[BaseMemory] = None, handle_parsing_errors: bool = True, output_key: str = "output", ) -> Text: @@ -52,7 +52,11 @@ async def run_agent( runnable = agent else: runnable = AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=handle_parsing_errors + agent=agent, # type: ignore + tools=tools, + verbose=True, + memory=memory, + handle_parsing_errors=handle_parsing_errors, ) input_dict = {"input": inputs} for var in input_variables: @@ -61,11 +65,11 @@ async def run_agent( result = await runnable.ainvoke(input_dict) self.status = result if output_key in result: - return result.get(output_key) + return cast(str, result.get(output_key)) elif "output" not in result: if output_key != "output": raise ValueError(f"Output key not found in result. Tried '{output_key}' and 'output'.") else: raise ValueError("Output key not found in result. Tried 'output'.") - return result.get("output") + return cast(str, result.get("output")) diff --git a/src/backend/base/langflow/base/models/model.py b/src/backend/base/langflow/base/models/model.py index 4e3a422b2b..7bbd614d68 100644 --- a/src/backend/base/langflow/base/models/model.py +++ b/src/backend/base/langflow/base/models/model.py @@ -1,10 +1,10 @@ -from typing import Optional +from typing import Optional, Union from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.language_models.llms import LLM from langchain_core.messages import HumanMessage, SystemMessage -from langflow.interface.custom.custom_component import CustomComponent +from langflow.custom import CustomComponent class LCModelComponent(CustomComponent): @@ -34,15 +34,15 @@ def get_result(self, runnable: LLM, stream: bool, input_value: str): def get_chat_result( self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None ): - messages = [] + messages: list[Union[HumanMessage, SystemMessage]] = [] if system_message: - messages.append(SystemMessage(system_message)) + messages.append(SystemMessage(content=system_message)) if input_value: - messages.append(HumanMessage(input_value)) + messages.append(HumanMessage(content=input_value)) if stream: - result = runnable.stream(messages) + return runnable.stream(messages) else: message = runnable.invoke(messages) result = message.content self.status = result - return result + return result diff --git a/src/backend/base/langflow/components/agents/XMLAgent.py b/src/backend/base/langflow/components/agents/XMLAgent.py index efd5cc0105..687bfff6fc 100644 --- a/src/backend/base/langflow/components/agents/XMLAgent.py +++ b/src/backend/base/langflow/components/agents/XMLAgent.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from langchain.agents import create_xml_agent from langchain_core.prompts import PromptTemplate @@ -69,7 +69,7 @@ async def build( llm: BaseLLM, tools: List[Tool], prompt: str, - memory: BaseMemory = None, + memory: Optional[BaseMemory] = None, tool_template: str = "{name}: {description}", handle_parsing_errors: bool = True, ) -> Text: diff --git a/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py b/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py index 07b21dabe2..e7c7739075 100644 --- a/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py +++ b/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py @@ -22,7 +22,7 @@ def build( self, request_timeout: Optional[float] = None, cohere_api_key: str = "", - max_retries: Optional[int] = None, + max_retries: int = 3, model: str = "embed-english-v2.0", truncate: Optional[str] = None, user_agent: str = "langchain", diff --git a/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py b/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py index 3578caef79..a7fc838b33 100644 --- a/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py +++ b/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py @@ -1,7 +1,6 @@ from typing import Any, Callable, Dict, List, Optional, Union from langchain_openai.embeddings.base import OpenAIEmbeddings -from pydantic.v1.types import SecretStr from langflow.field_typing import NestedDict from langflow.interface.custom.custom_component import CustomComponent @@ -100,8 +99,6 @@ def build( if disallowed_special == ["all"]: disallowed_special = "all" # type: ignore - api_key = SecretStr(openai_api_key) if openai_api_key else None - return OpenAIEmbeddings( tiktoken_enabled=tiktoken_enable, default_headers=default_headers, @@ -116,7 +113,7 @@ def build( model=model, model_kwargs=model_kwargs, base_url=openai_api_base, - api_key=api_key, + api_key=openai_api_key, openai_api_type=openai_api_type, api_version=openai_api_version, organization=openai_organization, diff --git a/src/backend/base/langflow/components/experimental/FlowTool.py b/src/backend/base/langflow/components/experimental/FlowTool.py index 541341d85a..ac48a0f038 100644 --- a/src/backend/base/langflow/components/experimental/FlowTool.py +++ b/src/backend/base/langflow/components/experimental/FlowTool.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, Text +from typing import Any, List, Optional from langchain_core.tools import StructuredTool from loguru import logger @@ -8,6 +8,7 @@ from langflow.graph.graph.base import Graph from langflow.helpers.flow import build_function_and_schema from langflow.schema.dotdict import dotdict +from langflow.schema.schema import Record class FlowToolComponent(CustomComponent): @@ -19,7 +20,7 @@ def get_flow_names(self) -> List[str]: flow_records = self.list_flows() return [flow_record.data["name"] for flow_record in flow_records] - def get_flow(self, flow_name: str) -> Optional[Text]: + def get_flow(self, flow_name: str) -> Optional[Record]: """ Retrieves a flow by its name. @@ -82,4 +83,4 @@ async def build(self, flow_name: str, name: str, description: str, return_direct description_repr = repr(tool.description).strip("'") args_str = "\n".join([f"- {arg_name}: {arg_data['description']}" for arg_name, arg_data in tool.args.items()]) self.status = f"{description_repr}\nArguments:\n{args_str}" - return tool + return tool # type: ignore diff --git a/src/backend/base/langflow/components/experimental/SubFlow.py b/src/backend/base/langflow/components/experimental/SubFlow.py index 150461275b..af309fa883 100644 --- a/src/backend/base/langflow/components/experimental/SubFlow.py +++ b/src/backend/base/langflow/components/experimental/SubFlow.py @@ -1,10 +1,12 @@ -from typing import Any, List, Optional, Text, Tuple +from typing import Any, List, Optional +from langflow.helpers.flow import get_flow_inputs from loguru import logger from langflow.custom import CustomComponent from langflow.graph.graph.base import Graph from langflow.graph.schema import ResultData, RunOutputs +from langflow.graph.vertex.base import Vertex from langflow.schema import Record from langflow.schema.dotdict import dotdict from langflow.template.field.base import TemplateField @@ -20,7 +22,7 @@ def get_flow_names(self) -> List[str]: flow_records = self.list_flows() return [flow_record.data["name"] for flow_record in flow_records] - def get_flow(self, flow_name: str) -> Optional[Text]: + def get_flow(self, flow_name: str) -> Optional[Record]: flow_records = self.list_flows() for flow_record in flow_records: if flow_record.data["name"] == flow_name: @@ -42,7 +44,7 @@ def update_build_config(self, build_config: dotdict, field_value: Any, field_nam raise ValueError(f"Flow {field_value} not found.") graph = Graph.from_payload(flow_record.data["data"]) # Get all inputs from the graph - inputs = self.get_flow_inputs(graph) + inputs = get_flow_inputs(graph) # Add inputs to the build config build_config = self.add_inputs_to_build_config(inputs, build_config) except Exception as e: @@ -50,21 +52,13 @@ def update_build_config(self, build_config: dotdict, field_value: Any, field_nam return build_config - def get_flow_inputs(self, graph: Graph) -> List[Record]: - inputs = [] - for vertex in graph.vertices: - if vertex.is_input: - inputs.append((vertex.id, vertex.display_name, vertex.description)) - logger.debug(inputs) - return inputs - - def add_inputs_to_build_config(self, inputs: List[Tuple], build_config: dotdict): + def add_inputs_to_build_config(self, inputs: List[Vertex], build_config: dotdict): new_fields: list[TemplateField] = [] - for input_id, input_display_name, input_description in inputs: + for vertex in inputs: field = TemplateField( - display_name=input_display_name, - name=input_id, - info=input_description, + display_name=vertex.display_name, + name=vertex.id, + info=vertex.description, field_type="str", default=None, ) @@ -110,12 +104,15 @@ async def build(self, flow_name: str, **kwargs) -> List[Record]: tweaks=tweaks, flow_name=flow_name, ) + if not run_outputs: + return [] run_output = run_outputs[0] records = [] - for output in run_output.outputs: - if output: - records.extend(self.build_records_from_result_data(output)) + if run_output is not None: + for output in run_output.outputs: + if output: + records.extend(self.build_records_from_result_data(output)) self.status = records logger.debug(records) diff --git a/src/backend/base/langflow/components/models/AzureOpenAIModel.py b/src/backend/base/langflow/components/models/AzureOpenAIModel.py index eeb488f039..58cc3f2c26 100644 --- a/src/backend/base/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/base/langflow/components/models/AzureOpenAIModel.py @@ -2,7 +2,6 @@ from langchain.llms.base import BaseLanguageModel from langchain_openai import AzureChatOpenAI -from pydantic.v1 import SecretStr from langflow.base.models.model import LCModelComponent from langflow.field_typing import Text @@ -91,21 +90,20 @@ def build( azure_endpoint: str, input_value: Text, azure_deployment: str, - api_key: str, api_version: str, + api_key: Optional[str] = None, system_message: Optional[str] = None, temperature: float = 0.7, max_tokens: Optional[int] = 1000, stream: bool = False, ) -> BaseLanguageModel: - secret_api_key = SecretStr(api_key) try: output = AzureChatOpenAI( model=model, azure_endpoint=azure_endpoint, azure_deployment=azure_deployment, api_version=api_version, - api_key=secret_api_key, + api_key=api_key, temperature=temperature, max_tokens=max_tokens, ) diff --git a/src/backend/base/langflow/components/models/base/__init__.py b/src/backend/base/langflow/components/models/base/__init__.py deleted file mode 100644 index 921f10336a..0000000000 --- a/src/backend/base/langflow/components/models/base/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .model import LCModelComponent - -__all__ = ["LCModelComponent"] diff --git a/src/backend/base/langflow/components/tools/SearchAPITool.py b/src/backend/base/langflow/components/tools/SearchAPITool.py index 6ec2f693ca..e0658c8c8d 100644 --- a/src/backend/base/langflow/components/tools/SearchAPITool.py +++ b/src/backend/base/langflow/components/tools/SearchAPITool.py @@ -34,4 +34,4 @@ def build( tool = SearchAPIRun(api_wrapper=search_api_wrapper) self.status = tool - return tool + return tool # type: ignore diff --git a/src/backend/base/langflow/components/vectorstores/AstraDB.py b/src/backend/base/langflow/components/vectorstores/AstraDB.py index 9ab73808e3..28f5ec22e9 100644 --- a/src/backend/base/langflow/components/vectorstores/AstraDB.py +++ b/src/backend/base/langflow/components/vectorstores/AstraDB.py @@ -1,6 +1,7 @@ from typing import List, Optional from langchain_astradb import AstraDBVectorStore +from langchain_astradb.utils.astradb import SetupMode from langflow.custom import CustomComponent from langflow.field_typing import Embeddings, VectorStore @@ -83,6 +84,10 @@ def build( metadata_indexing_exclude: Optional[List[str]] = None, collection_indexing_policy: Optional[dict] = None, ) -> VectorStore: + try: + setup_mode_value = SetupMode[setup_mode.upper()] + except KeyError: + raise ValueError(f"Invalid setup mode: {setup_mode}") if inputs: documents = [_input.to_lc_document() for _input in inputs] @@ -98,7 +103,7 @@ def build( bulk_insert_batch_concurrency=bulk_insert_batch_concurrency, bulk_insert_overwrite_concurrency=bulk_insert_overwrite_concurrency, bulk_delete_concurrency=bulk_delete_concurrency, - setup_mode=setup_mode, + setup_mode=setup_mode_value, pre_delete_collection=pre_delete_collection, metadata_indexing_include=metadata_indexing_include, metadata_indexing_exclude=metadata_indexing_exclude, @@ -116,7 +121,7 @@ def build( bulk_insert_batch_concurrency=bulk_insert_batch_concurrency, bulk_insert_overwrite_concurrency=bulk_insert_overwrite_concurrency, bulk_delete_concurrency=bulk_delete_concurrency, - setup_mode=setup_mode, + setup_mode=setup_mode_value, pre_delete_collection=pre_delete_collection, metadata_indexing_include=metadata_indexing_include, metadata_indexing_exclude=metadata_indexing_exclude, diff --git a/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py b/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py index 8432f01f15..3343d91f64 100644 --- a/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py +++ b/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py @@ -6,7 +6,7 @@ from langflow.schema import Record -class AstraDBSearchComponent(AstraDBVectorStoreComponent, LCVectorStoreComponent): +class AstraDBSearchComponent(LCVectorStoreComponent): display_name = "AstraDB Search" description = "Searches an existing AstraDB Vector Store" @@ -74,7 +74,7 @@ def build( self, embedding: Embeddings, collection_name: str, - input_value: Optional[Text] = None, + input_value: Text, search_type: str = "Similarity", token: Optional[str] = None, api_endpoint: Optional[str] = None, @@ -90,7 +90,7 @@ def build( metadata_indexing_exclude: Optional[List[str]] = None, collection_indexing_policy: Optional[dict] = None, ) -> List[Record]: - vector_store = super().build( + vector_store = AstraDBVectorStoreComponent().build( embedding=embedding, collection_name=collection_name, token=token, diff --git a/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py b/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py index 10b69ce153..cbb583fb36 100644 --- a/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py +++ b/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py @@ -6,7 +6,7 @@ from langflow.schema import Record -class MongoDBAtlasSearchComponent(MongoDBAtlasComponent, LCVectorStoreComponent): +class MongoDBAtlasSearchComponent(LCVectorStoreComponent): display_name = "MongoDB Atlas Search" description = "Search a MongoDB Atlas Vector Store for similar documents." @@ -37,9 +37,10 @@ def build( # type: ignore[override] search_kwargs: Optional[NestedDict] = None, ) -> List[Record]: search_kwargs = search_kwargs or {} - vector_store = super().build( - connection_string=mongodb_atlas_cluster_uri, - namespace=f"{db_name}.{collection_name}", + vector_store = MongoDBAtlasComponent().build( + mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri, + collection_name=collection_name, + db_name=db_name, embedding=embedding, index_name=index_name, ) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 174ec9ce45..00bb69f674 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1,7 +1,7 @@ import asyncio from collections import defaultdict, deque from itertools import chain -from typing import TYPE_CHECKING, Coroutine, Dict, Generator, List, Optional, Type, Union +from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Generator, List, Literal, Optional, Type, Union from loguru import logger @@ -201,7 +201,7 @@ async def _run( self, inputs: Dict[str, str], input_components: list[str], - input_type: str, + input_type: Literal["chat", "text", "json", "any"] | None, outputs: list[str], stream: bool, session_id: str, @@ -236,7 +236,7 @@ async def _run( continue # If the input_type is not any and the input_type is not in the vertex id # Example: input_type = "chat" and vertex.id = "OpenAI-19ddn" - elif input_type != "any" and input_type not in vertex.id.lower(): + elif input_type is not None and input_type != "any" and input_type not in vertex.id.lower(): continue if vertex is None: raise ValueError(f"Vertex {vertex_id} not found") @@ -269,9 +269,9 @@ async def _run( def run( self, - inputs: Dict[str, str], - input_components: Optional[list[str]] = None, - types: Optional[list[str]] = None, + inputs: list[Dict[str, str]], + input_components: Optional[list[list[str]]] = None, + types: Optional[list[Literal["chat", "text", "json", "any"] | None]] = None, outputs: Optional[list[str]] = None, session_id: Optional[str] = None, stream: bool = False, @@ -309,7 +309,7 @@ async def arun( self, inputs: list[Dict[str, str]], inputs_components: Optional[list[list[str]]] = None, - types: Optional[list[str]] = None, + types: Optional[list[Literal["chat", "text", "json", "any"] | None]] = None, outputs: Optional[list[str]] = None, session_id: Optional[str] = None, stream: bool = False, @@ -338,8 +338,12 @@ async def arun( inputs = [{}] # Length of all should be the as inputs length # just add empty lists to complete the length + if inputs_components is None: + inputs_components = [] for _ in range(len(inputs) - len(inputs_components)): inputs_components.append([]) + if types is None: + types = [] for _ in range(len(inputs) - len(types)): types.append("any") for run_inputs, components, input_type in zip(inputs, inputs_components, types): @@ -650,7 +654,7 @@ def get_vertex(self, vertex_id: str) -> Vertex: async def build_vertex( self, lock: asyncio.Lock, - set_cache_coro: Coroutine, + set_cache_coro: Callable[["Graph", asyncio.Lock], Coroutine], vertex_id: str, inputs_dict: Optional[Dict[str, str]] = None, user_id: Optional[str] = None, @@ -693,7 +697,9 @@ async def build_vertex( logger.exception(f"Error building vertex: {exc}") raise exc - async def get_next_and_top_level_vertices(self, lock: asyncio.Lock, set_cache_coro: Coroutine, vertex: Vertex): + async def get_next_and_top_level_vertices( + self, lock: asyncio.Lock, set_cache_coro: Callable[["Graph", asyncio.Lock], Coroutine], vertex: Vertex + ): """ Retrieves the next runnable vertices and the top level vertices for a given vertex. diff --git a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py index 3fa9d3e913..d31f01fe75 100644 --- a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py +++ b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py @@ -1,6 +1,6 @@ import asyncio from collections import defaultdict -from typing import TYPE_CHECKING, Coroutine, List +from typing import TYPE_CHECKING, Awaitable, Callable, List if TYPE_CHECKING: from langflow.graph.graph.base import Graph @@ -55,7 +55,7 @@ def update_vertex_run_state(self, vertex_id: str, is_runnable: bool): async def get_next_runnable_vertices( self, lock: asyncio.Lock, - set_cache_coro: Coroutine, + set_cache_coro: Callable[["Graph", asyncio.Lock], Awaitable[None]], graph: "Graph", vertex: "Vertex", ): @@ -85,7 +85,7 @@ async def get_next_runnable_vertices( for v_id in set(next_runnable_vertices): # Use set to avoid duplicates self.update_vertex_run_state(v_id, is_runnable=False) self.remove_from_predecessors(v_id) - await set_cache_coro(data=graph, lock=lock) + await set_cache_coro(graph, lock) return next_runnable_vertices @staticmethod diff --git a/src/backend/base/langflow/alembic/helpers/__init__.py b/src/backend/base/langflow/helpers/__init__.py similarity index 100% rename from src/backend/base/langflow/alembic/helpers/__init__.py rename to src/backend/base/langflow/helpers/__init__.py diff --git a/src/backend/base/langflow/helpers/__init__py b/src/backend/base/langflow/helpers/__init__py deleted file mode 100644 index b944a01788..0000000000 --- a/src/backend/base/langflow/helpers/__init__py +++ /dev/null @@ -1,3 +0,0 @@ -from .record import docs_to_records, records_to_text - -__all__ = ["docs_to_records", "records_to_text"] \ No newline at end of file diff --git a/src/backend/base/langflow/helpers/flow.py b/src/backend/base/langflow/helpers/flow.py index 328e99ca16..88c51efa30 100644 --- a/src/backend/base/langflow/helpers/flow.py +++ b/src/backend/base/langflow/helpers/flow.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional, Tuple, Type, Union, cast from pydantic.v1 import BaseModel, Field, create_model from sqlmodel import select @@ -63,28 +63,30 @@ def find_flow(flow_name: str, user_id: str) -> Optional[str]: async def run_flow( - inputs: Union[dict, List[dict]] = None, + inputs: Optional[Union[dict, List[dict]]] = None, tweaks: Optional[dict] = None, flow_id: Optional[str] = None, flow_name: Optional[str] = None, user_id: Optional[str] = None, ) -> Any: + if not user_id: + raise ValueError("Session is invalid") graph = await load_flow(user_id, flow_id, flow_name, tweaks) if inputs is None: inputs = [] - inputs_list = [] + inputs_list: list[dict[str, str]] = [] inputs_components = [] types = [] for input_dict in inputs: - inputs_list.append({INPUT_FIELD_NAME: input_dict.get("input_value")}) + inputs_list.append({INPUT_FIELD_NAME: cast(str, input_dict.get("input_value", ""))}) inputs_components.append(input_dict.get("components", [])) types.append(input_dict.get("type", [])) return await graph.arun(inputs_list, inputs_components=inputs_components, types=types) -def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Coroutine: +def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Callable[..., Awaitable[Any]]: """ Generate a dynamic flow function based on the given inputs and flow ID. @@ -138,12 +140,14 @@ async def flow_function({func_args}): """ compiled_func = compile(func_body, "", "exec") - local_scope = {} + local_scope: dict = {} exec(compiled_func, globals(), local_scope) return local_scope["flow_function"] -def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, BaseModel]: +def build_function_and_schema( + flow_record: Record, graph: "Graph" +) -> Tuple[Callable[..., Awaitable[Any]], Type[BaseModel]]: """ Builds a dynamic function and schema for a given flow. @@ -178,7 +182,7 @@ def get_flow_inputs(graph: "Graph") -> List["Vertex"]: return inputs -def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> BaseModel: +def build_schema_from_inputs(name: str, inputs: List["Vertex"]) -> Type[BaseModel]: """ Builds a schema from the given inputs. @@ -196,4 +200,4 @@ def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> B field_name = input_.display_name.lower().replace(" ", "_") description = input_.description fields[field_name] = (str, Field(default="", description=description)) - return create_model(name, **fields) + return create_model(name, **fields) # type: ignore diff --git a/src/backend/base/langflow/interface/custom/custom_component/custom_component.py b/src/backend/base/langflow/interface/custom/custom_component/custom_component.py index 1554a57daf..4276d4616e 100644 --- a/src/backend/base/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/interface/custom/custom_component/custom_component.py @@ -423,11 +423,13 @@ def get_function(self): return validate.create_function(self.code, self.function_entrypoint_name) async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> "Graph": - return await load_flow(flow_id, tweaks) + if not self._user_id: + raise ValueError("Session is invalid") + return await load_flow(user_id=self._user_id, flow_id=flow_id, tweaks=tweaks) async def run_flow( self, - inputs: Union[dict, List[dict]] = None, + inputs: Optional[Union[dict, List[dict]]] = None, flow_id: Optional[str] = None, flow_name: Optional[str] = None, tweaks: Optional[dict] = None, diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index 0ba40b90e3..55940622c2 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -38,7 +38,7 @@ async def instantiate_class( user_id=None, ) -> Any: """Instantiate class from module type and key, and params""" - from langflow.legacy_custom.customs import CUSTOM_NODES + from langflow.interface.custom_lists import CUSTOM_NODES vertex_type = vertex.vertex_type base_type = vertex.base_type @@ -50,7 +50,9 @@ async def instantiate_class( if custom_node := CUSTOM_NODES.get(vertex_type): if hasattr(custom_node, "initialize"): return custom_node.initialize(**params) - return custom_node(**params) + if callable(custom_node): + return custom_node(**params) + raise ValueError(f"Custom node {vertex_type} is not callable") logger.debug(f"Instantiating {vertex_type} of type {base_type}") if not base_type: raise ValueError("No base type provided for vertex") diff --git a/src/backend/base/langflow/interface/tools/base.py b/src/backend/base/langflow/interface/tools/base.py index 2506c01706..ddaccb4bbd 100644 --- a/src/backend/base/langflow/interface/tools/base.py +++ b/src/backend/base/langflow/interface/tools/base.py @@ -5,8 +5,6 @@ from langflow.interface.tools.constants import ALL_TOOLS_NAMES, CUSTOM_TOOLS, FILE_TOOLS, OTHER_TOOLS from langflow.interface.tools.util import get_tool_params from langflow.legacy_custom import customs -from langflow.interface.tools.util import get_tool_params -from langflow.legacy_custom import customs from langflow.services.deps import get_settings_service from langflow.template.field.base import TemplateField from langflow.template.template.base import Template diff --git a/src/backend/base/langflow/legacy_custom/customs.py b/src/backend/base/langflow/legacy_custom/customs.py index 82e87f1e55..ff69064ffd 100644 --- a/src/backend/base/langflow/legacy_custom/customs.py +++ b/src/backend/base/langflow/legacy_custom/customs.py @@ -1,7 +1,7 @@ from langflow.template import frontend_node # These should always be instantiated -CUSTOM_NODES = { +CUSTOM_NODES: dict[str, dict[str, frontend_node.base.FrontendNode]] = { # "prompts": { # "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(), # }, diff --git a/src/backend/base/langflow/processing/process.py b/src/backend/base/langflow/processing/process.py index fa499472fa..c7e496dd77 100644 --- a/src/backend/base/langflow/processing/process.py +++ b/src/backend/base/langflow/processing/process.py @@ -125,7 +125,7 @@ class Result(BaseModel): async def run_graph( - graph: Union["Graph", dict], + graph: "Graph", flow_id: str, stream: bool, session_id: Optional[str] = None, diff --git a/src/backend/base/langflow/services/factory.py b/src/backend/base/langflow/services/factory.py index 4780b53c78..49bea7db24 100644 --- a/src/backend/base/langflow/services/factory.py +++ b/src/backend/base/langflow/services/factory.py @@ -23,7 +23,7 @@ def create(self, *args, **kwargs) -> "Service": raise self.service_class(*args, **kwargs) -def hash_factory(factory: ServiceFactory) -> str: +def hash_factory(factory: Type[ServiceFactory]) -> str: return factory.service_class.__name__ @@ -38,7 +38,7 @@ def hash_infer_service_types_args(factory_class: Type[ServiceFactory], available @cached(cache=LRUCache(maxsize=10), key=hash_infer_service_types_args) -def infer_service_types(factory_class: Type[ServiceFactory], available_services=None) -> "ServiceType": +def infer_service_types(factory_class: Type[ServiceFactory], available_services=None) -> list["ServiceType"]: create_method = factory_class.create type_hints = get_type_hints(create_method, globalns=available_services) service_types = [] diff --git a/src/backend/base/langflow/services/settings/auth.py b/src/backend/base/langflow/services/settings/auth.py index 3dd92a04ab..a5bd7ed804 100644 --- a/src/backend/base/langflow/services/settings/auth.py +++ b/src/backend/base/langflow/services/settings/auth.py @@ -1,5 +1,6 @@ import secrets from pathlib import Path +from typing import Literal from loguru import logger from passlib.context import CryptContext @@ -14,7 +15,7 @@ class AuthSettings(BaseSettings): # Login settings CONFIG_DIR: str SECRET_KEY: SecretStr = Field( - default=None, + default=SecretStr(""), description="Secret key for JWT. If not provided, a random one will be generated.", frozen=False, ) @@ -33,13 +34,13 @@ class AuthSettings(BaseSettings): SUPERUSER: str = DEFAULT_SUPERUSER SUPERUSER_PASSWORD: str = DEFAULT_SUPERUSER_PASSWORD - REFRESH_SAME_SITE: str = "none" + REFRESH_SAME_SITE: Literal["lax", "strict", "none"] = "none" """The SameSite attribute of the refresh token cookie.""" REFRESH_SECURE: bool = True """The Secure attribute of the refresh token cookie.""" REFRESH_HTTPONLY: bool = True """The HttpOnly attribute of the refresh token cookie.""" - ACCESS_SAME_SITE: str = "none" + ACCESS_SAME_SITE: Literal["lax", "strict", "none"] = "none" """The SameSite attribute of the access token cookie.""" ACCESS_SECURE: bool = True """The Secure attribute of the access token cookie.""" @@ -85,9 +86,10 @@ def get_secret_key(cls, value, values): secret_key_path = Path(config_dir) / "secret_key" - if value: + if value and isinstance(value, SecretStr): logger.debug("Secret key provided") - write_secret_to_file(secret_key_path, value) + secret_value = value.get_secret_value() + write_secret_to_file(secret_key_path, secret_value) else: logger.debug("No secret key provided, generating a random one") @@ -103,4 +105,4 @@ def get_secret_key(cls, value, values): write_secret_to_file(secret_key_path, value) logger.debug("Saved secret key") - return value + return value if isinstance(value, SecretStr) else SecretStr(value) diff --git a/src/backend/base/langflow/template/frontend_node/__init__.py b/src/backend/base/langflow/template/frontend_node/__init__.py index 00c2766962..ceb2e0cb9b 100644 --- a/src/backend/base/langflow/template/frontend_node/__init__.py +++ b/src/backend/base/langflow/template/frontend_node/__init__.py @@ -10,10 +10,12 @@ textsplitters, tools, vectorstores, + base, ) __all__ = [ "agents", + "base", "chains", "embeddings", "memories", diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index d32780505c..df6daa0dcf 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -17,7 +17,7 @@ repository = "https://github.com/logspace-ai/langflow" license = "MIT" readme = "README.md" keywords = ["nlp", "langchain", "openai", "gpt", "gui"] -packages = [{ include = "langflow" }, { include = "py.typed" }] +packages = [{ include = "langflow" }, { include = "langflow/py.typed" }] include = ["pyproject.toml", "README.md", "langflow/**/*"] documentation = "https://docs.langflow.org" diff --git a/src/backend/langflow/base/agents/__init__.py b/src/backend/langflow/base/agents/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/langflow/base/agents/agent.py b/src/backend/langflow/base/agents/agent.py deleted file mode 100644 index 8dac73e61c..0000000000 --- a/src/backend/langflow/base/agents/agent.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import List, Union - -from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent - -from langflow import CustomComponent -from langflow.field_typing import BaseMemory, Text, Tool - - -class LCAgentComponent(CustomComponent): - def build_config(self): - return { - "lc": { - "display_name": "LangChain", - "info": "The LangChain to interact with.", - }, - "handle_parsing_errors": { - "display_name": "Handle Parsing Errors", - "info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.", - "advanced": True, - }, - "output_key": { - "display_name": "Output Key", - "info": "The key to use to get the output from the agent.", - "advanced": True, - }, - "memory": { - "display_name": "Memory", - "info": "Memory to use for the agent.", - }, - "tools": { - "display_name": "Tools", - "info": "Tools the agent can use.", - }, - "input_value": { - "display_name": "Input", - "info": "Input text to pass to the agent.", - }, - } - - async def run_agent( - self, - agent: Union[BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor], - inputs: str, - input_variables: list[str], - tools: List[Tool], - memory: BaseMemory = None, - handle_parsing_errors: bool = True, - output_key: str = "output", - ) -> Text: - if isinstance(agent, AgentExecutor): - runnable = agent - else: - runnable = AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=handle_parsing_errors - ) - input_dict = {"input": inputs} - for var in input_variables: - if var not in ["agent_scratchpad", "input"]: - input_dict[var] = "" - result = await runnable.ainvoke(input_dict) - self.status = result - if output_key in result: - return result.get(output_key) - elif "output" not in result: - if output_key != "output": - raise ValueError(f"Output key not found in result. Tried '{output_key}' and 'output'.") - else: - raise ValueError("Output key not found in result. Tried 'output'.") - - return result.get("output") diff --git a/src/backend/langflow/base/models/__init__.py b/src/backend/langflow/base/models/__init__.py deleted file mode 100644 index 921f10336a..0000000000 --- a/src/backend/langflow/base/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .model import LCModelComponent - -__all__ = ["LCModelComponent"] diff --git a/src/backend/langflow/base/models/model.py b/src/backend/langflow/base/models/model.py deleted file mode 100644 index e2ab4b6cf8..0000000000 --- a/src/backend/langflow/base/models/model.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Optional - -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.language_models.llms import LLM -from langchain_core.messages import HumanMessage, SystemMessage - -from langflow import CustomComponent - - -class LCModelComponent(CustomComponent): - display_name: str = "Model Name" - description: str = "Model Description" - - def get_result(self, runnable: LLM, stream: bool, input_value: str): - """ - Retrieves the result from the output of a Runnable object. - - Args: - output (Runnable): The output object to retrieve the result from. - stream (bool): Indicates whether to use streaming or invocation mode. - input_value (str): The input value to pass to the output object. - - Returns: - The result obtained from the output object. - """ - if stream: - result = runnable.stream(input_value) - else: - message = runnable.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result - - def get_chat_result( - self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None - ): - messages = [] - if input_value: - messages.append(HumanMessage(input_value)) - if system_message: - messages.append(SystemMessage(system_message)) - if stream: - result = runnable.stream(messages) - else: - message = runnable.invoke(messages) - result = message.content - self.status = result - return result diff --git a/src/backend/langflow/components/experimental/FlowTool.py b/src/backend/langflow/components/experimental/FlowTool.py deleted file mode 100644 index 5f504ff027..0000000000 --- a/src/backend/langflow/components/experimental/FlowTool.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Any, List, Optional, Text - -from langchain_core.tools import StructuredTool -from loguru import logger - -from langflow import CustomComponent -from langflow.field_typing import Tool -from langflow.graph.graph.base import Graph -from langflow.helpers.flow import build_function_and_schema -from langflow.schema.dotdict import dotdict - - -class FlowToolComponent(CustomComponent): - display_name = "Flow as Tool" - description = "Construct a Tool from a function that runs the loaded Flow." - field_order = ["flow_name", "name", "description", "return_direct"] - - def get_flow_names(self) -> List[str]: - flow_records = self.list_flows() - return [flow_record.data["name"] for flow_record in flow_records] - - def get_flow(self, flow_name: str) -> Optional[Text]: - """ - Retrieves a flow by its name. - - Args: - flow_name (str): The name of the flow to retrieve. - - Returns: - Optional[Text]: The flow record if found, None otherwise. - """ - flow_records = self.list_flows() - for flow_record in flow_records: - if flow_record.data["name"] == flow_name: - return flow_record - return None - - def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None): - logger.debug(f"Updating build config with field value {field_value} and field name {field_name}") - if field_name == "flow_name": - build_config["flow_name"]["options"] = self.get_flow_names() - - return build_config - - def build_config(self): - return { - "flow_name": { - "display_name": "Flow Name", - "info": "The name of the flow to run.", - "options": [], - "real_time_refresh": True, - "refresh_button": True, - }, - "name": { - "display_name": "Name", - "description": "The name of the tool.", - }, - "description": { - "display_name": "Description", - "description": "The description of the tool.", - }, - "return_direct": { - "display_name": "Return Direct", - "description": "Return the result directly from the Tool.", - "advanced": True, - }, - } - - async def build(self, flow_name: str, name: str, description: str, return_direct: bool = False) -> Tool: - flow_record = self.get_flow(flow_name) - if not flow_record: - raise ValueError("Flow not found.") - graph = Graph.from_payload(flow_record.data["data"]) - dynamic_flow_function, schema = build_function_and_schema(flow_record, graph) - tool = StructuredTool.from_function( - coroutine=dynamic_flow_function, - name=name, - description=description, - return_direct=return_direct, - args_schema=schema, - ) - description_repr = repr(tool.description).strip("'") - args_str = "\n".join([f"- {arg_name}: {arg_data['description']}" for arg_name, arg_data in tool.args.items()]) - self.status = f"{description_repr}\nArguments:\n{args_str}" - return tool diff --git a/src/backend/langflow/components/experimental/Schema.py b/src/backend/langflow/components/experimental/Schema.py deleted file mode 100644 index 72cbec23e2..0000000000 --- a/src/backend/langflow/components/experimental/Schema.py +++ /dev/null @@ -1,25 +0,0 @@ -from langflow.custom import CustomComponent - - -class SchemaComponent(CustomComponent): - display_name = "Schema" - description = "Construct a Schema from a list of fields." - - def build_config(self): - return { - "fields": { - "display_name": "Fields", - "info": "The fields to include in the schema.", - }, - "name": { - "display_name": "Name", - "info": "The name of the schema.", - }, - } - - def build(self, name: str, fields: list[dict]): - # The idea for this component is to use create_model from pydantic to create a schema - # from a list of fields. This will be useful for creating schemas for the flow tool. - pass - - # field is a simple list of dictionaries with the field name and diff --git a/src/backend/langflow/components/tools/SearchAPITool.py b/src/backend/langflow/components/tools/SearchAPITool.py deleted file mode 100644 index 76c82ad9c5..0000000000 --- a/src/backend/langflow/components/tools/SearchAPITool.py +++ /dev/null @@ -1,37 +0,0 @@ -from langchain_community.tools.searchapi import SearchAPIRun -from langchain_community.utilities.searchapi import SearchApiAPIWrapper - -from langflow import CustomComponent -from langflow.field_typing import Tool - - -class SearchApiToolComponent(CustomComponent): - display_name: str = "SearchApi Tool" - description: str = "Real-time search engine results API." - documentation: str = "https://www.searchapi.io/docs/google" - field_config = { - "engine": { - "display_name": "Engine", - "field_type": "str", - "info": "The search engine to use.", - }, - "api_key": { - "display_name": "API Key", - "field_type": "str", - "required": True, - "password": True, - "info": "The API key to use SearchApi.", - }, - } - - def build( - self, - engine: str, - api_key: str, - ) -> Tool: - search_api_wrapper = SearchApiAPIWrapper(engine=engine, searchapi_api_key=api_key) - - tool = SearchAPIRun(api_wrapper=search_api_wrapper) - - self.status = tool - return tool diff --git a/src/backend/langflow/version/__init__.py b/src/backend/langflow/version/__init__.py new file mode 100644 index 0000000000..5c0cf285cb --- /dev/null +++ b/src/backend/langflow/version/__init__.py @@ -0,0 +1 @@ +from .version import __version__ # noqa: F401 diff --git a/src/backend/base/langflow/version.py b/src/backend/langflow/version/version.py similarity index 53% rename from src/backend/base/langflow/version.py rename to src/backend/langflow/version/version.py index c8bb365cac..3aaf5f9737 100644 --- a/src/backend/base/langflow/version.py +++ b/src/backend/langflow/version/version.py @@ -3,6 +3,5 @@ try: __version__ = metadata.version(__package__) except metadata.PackageNotFoundError: - # Case where package metadata is not available. __version__ = "" -del metadata # optional, avoids polluting the results of dir(__package__) +del metadata