diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 64d06a313..d4eaafb95 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,7 +3,15 @@ name: lint on: push: branches: [main] + paths: + - "poetry.lock" + - "pyproject.toml" + - "src/backend/**" pull_request: + paths: + - "poetry.lock" + - "pyproject.toml" + - "src/backend/**" env: POETRY_VERSION: "1.7.0" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93ec5bf7e..10ab9b324 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,16 @@ name: test on: push: branches: [main] + paths: + - "poetry.lock" + - "pyproject.toml" + - "src/backend/**" pull_request: branches: [dev] + paths: + - "poetry.lock" + - "pyproject.toml" + - "src/backend/**" env: POETRY_VERSION: "1.5.0" diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index e1b52aeb5..10b9d9e38 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -1,10 +1,11 @@ import time import uuid -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Annotated, Optional from fastapi import ( APIRouter, BackgroundTasks, + Body, Depends, HTTPException, WebSocket, @@ -21,6 +22,7 @@ from langflow.api.utils import ( format_exception_message, ) from langflow.api.v1.schemas import ( + InputValueRequest, ResultDataResponse, StreamData, VertexBuildResponse, @@ -32,8 +34,9 @@ from langflow.services.auth.utils import ( get_current_user_for_websocket, ) from langflow.services.chat.service import ChatService -from langflow.services.deps import get_chat_service, get_session +from langflow.services.deps import get_chat_service, get_session, get_session_service from langflow.services.monitor.utils import log_vertex_build +from langflow.services.session.service import SessionService if TYPE_CHECKING: from langflow.graph.vertex.types import ChatVertex @@ -138,10 +141,12 @@ async def build_vertex( flow_id: str, vertex_id: str, background_tasks: BackgroundTasks, + inputs: Annotated[InputValueRequest, Body(embed=True)] = None, chat_service: "ChatService" = Depends(get_chat_service), current_user=Depends(get_current_active_user), ): """Build a vertex instead of the entire graph.""" + {"inputs": {"input_value": "some value"}} start_time = time.perf_counter() try: start_time = time.perf_counter() @@ -162,7 +167,8 @@ async def build_vertex( vertex = graph.get_vertex(vertex_id) try: if not vertex.pinned or not vertex._built: - await vertex.build(user_id=current_user.id) + inputs_dict = inputs.model_dump() if inputs else {} + await vertex.build(user_id=current_user.id, inputs=inputs_dict) if vertex.result is not None: params = vertex._built_object_repr() @@ -175,7 +181,7 @@ async def build_vertex( result_data_response = ResultDataResponse(**result_dict.model_dump()) except Exception as exc: - logger.error(f"Error building vertex: {exc}") + logger.exception(f"Error building vertex: {exc}") params = format_exception_message(exc) valid = False result_data_response = ResultDataResponse(results={}) @@ -185,15 +191,16 @@ async def build_vertex( chat_service.clear_cache(flow_id) # Log the vertex build - background_tasks.add_task( - log_vertex_build, - flow_id=flow_id, - vertex_id=vertex_id, - valid=valid, - params=params, - data=result_data_response, - artifacts=artifacts, - ) + if not vertex.will_stream: + background_tasks.add_task( + log_vertex_build, + flow_id=flow_id, + vertex_id=vertex_id, + valid=valid, + params=params, + data=result_data_response, + artifacts=artifacts, + ) timedelta = time.perf_counter() - start_time duration = format_elapsed_time(timedelta) @@ -226,39 +233,56 @@ async def build_vertex( async def build_vertex_stream( flow_id: str, vertex_id: str, + session_id: Optional[str] = None, chat_service: "ChatService" = Depends(get_chat_service), + session_service: "SessionService" = Depends(get_session_service), ): """Build a vertex instead of the entire graph.""" try: async def stream_vertex(): try: - cache = chat_service.get_cache(flow_id) - if not cache: - # If there's no cache - raise ValueError(f"No cache found for {flow_id}.") + if not session_id: + cache = chat_service.get_cache(flow_id) + if not cache: + # If there's no cache + raise ValueError(f"No cache found for {flow_id}.") + else: + graph = cache.get("result") else: - graph = cache.get("result") + session_data = await session_service.load_session(session_id) + graph, artifacts = session_data if session_data else (None, None) + if not graph: + raise ValueError(f"No graph found for {flow_id}.") vertex: "ChatVertex" = graph.get_vertex(vertex_id) if not hasattr(vertex, "stream"): raise ValueError(f"Vertex {vertex_id} does not support streaming") - if not vertex.pinned or not vertex._built: + if isinstance(vertex._built_result, str) and vertex._built_result: + stream_data = StreamData( + event="message", + data={"message": f"Streaming vertex {vertex_id}"}, + ) + yield str(stream_data) + stream_data = StreamData( + event="message", + data={"chunk": vertex._built_result}, + ) + yield str(stream_data) + + elif not vertex.pinned or not vertex._built: logger.debug(f"Streaming vertex {vertex_id}") stream_data = StreamData( event="message", data={"message": f"Streaming vertex {vertex_id}"}, ) yield str(stream_data) - number_of_chunks = 0 async for chunk in vertex.stream(): stream_data = StreamData( event="message", data={"chunk": chunk}, ) - number_of_chunks += 1 yield str(stream_data) - logger.debug(f"Number of chunks: {number_of_chunks}") elif vertex.result is not None: stream_data = StreamData( event="message", diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index fce4f9649..116c63b2c 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -228,6 +228,7 @@ async def run_flow_with_caching( flow_id: str, inputs: Optional[Union[List[dict], dict]] = None, tweaks: Optional[dict] = None, + stream: Annotated[bool, Body(embed=True)] = False, # noqa: F821 session_id: Annotated[Union[None, str], Body(embed=True)] = None, # noqa: F821 api_key_user: User = Depends(api_key_security), session_service: SessionService = Depends(get_session_service), @@ -239,13 +240,14 @@ async def run_flow_with_caching( task_result: Any = None if not graph: raise ValueError("Graph not found in the session") - task_result = await run_graph( + task_result, session_id = await run_graph( graph=graph, flow_id=flow_id, session_id=session_id, inputs=inputs, artifacts=artifacts, session_service=session_service, + stream=stream, ) else: @@ -263,13 +265,14 @@ async def run_flow_with_caching( raise ValueError(f"Flow {flow_id} has no data") graph_data = flow.data graph_data = process_tweaks(graph_data, tweaks) - task_result = await run_graph( + task_result, session_id = await run_graph( graph=graph_data, flow_id=flow_id, session_id=session_id, inputs=inputs, artifacts={}, session_service=session_service, + stream=stream, ) return RunResponse(outputs=task_result, session_id=session_id) diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 0092efa4e..c85f946da 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -261,3 +261,7 @@ class VertexBuildResponse(BaseModel): class VerticesBuiltResponse(BaseModel): vertices: List[VertexBuildResponse] + + +class InputValueRequest(BaseModel): + input_value: str diff --git a/src/backend/langflow/components/agents/OpenAIConversationalAgent.py b/src/backend/langflow/components/agents/OpenAIConversationalAgent.py index 5dfb53387..5a411ecec 100644 --- a/src/backend/langflow/components/agents/OpenAIConversationalAgent.py +++ b/src/backend/langflow/components/agents/OpenAIConversationalAgent.py @@ -16,7 +16,8 @@ from langflow.field_typing.range_spec import RangeSpec class ConversationalAgent(CustomComponent): display_name: str = "OpenAI Conversational Agent" description: str = "Conversational Agent that can use OpenAI's function calling API" - + icon = "OpenAI" + def build_config(self): openai_function_models = [ "gpt-4-turbo-preview", diff --git a/src/backend/langflow/components/chains/RetrievalQA.py b/src/backend/langflow/components/chains/RetrievalQA.py index 4968afe87..53fa24f15 100644 --- a/src/backend/langflow/components/chains/RetrievalQA.py +++ b/src/backend/langflow/components/chains/RetrievalQA.py @@ -20,7 +20,10 @@ class RetrievalQAComponent(CustomComponent): "input_key": {"display_name": "Input Key", "advanced": True}, "output_key": {"display_name": "Output Key", "advanced": True}, "return_source_documents": {"display_name": "Return Source Documents"}, - "inputs": {"display_name": "Input", "input_types": ["Text", "Document"]}, + "input_value": { + "display_name": "Input", + "input_types": ["Text", "Document"], + }, } def build( diff --git a/src/backend/langflow/components/documentloaders/GatherRecords.py b/src/backend/langflow/components/documentloaders/GatherRecords.py index e25a2d189..745d0655b 100644 --- a/src/backend/langflow/components/documentloaders/GatherRecords.py +++ b/src/backend/langflow/components/documentloaders/GatherRecords.py @@ -1,6 +1,6 @@ from concurrent import futures from pathlib import Path -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from langflow import CustomComponent from langflow.schema import Record @@ -12,21 +12,30 @@ class GatherRecordsComponent(CustomComponent): def build_config(self) -> Dict[str, Any]: return { + "path": {"display_name": "Path"}, + "types": { + "display_name": "Types", + "info": "File types to load. Leave empty to load all types.", + }, + "depth": {"display_name": "Depth", "info": "Depth to search for files."}, + "max_concurrency": {"display_name": "Max Concurrency", "advanced": True}, "load_hidden": { - "display_name": "Load Hidden Files", - "value": False, + "display_name": "Load Hidden", "advanced": True, + "info": "If true, hidden files will be loaded.", }, - "max_concurrency": { - "display_name": "Max Concurrency", - "value": 10, + "recursive": { + "display_name": "Recursive", "advanced": True, + "info": "If true, the search will be recursive.", + }, + "silent_errors": { + "display_name": "Silent Errors", + "advanced": True, + "info": "If true, errors will not raise an exception.", }, - "path": {"display_name": "Local Directory"}, - "recursive": {"display_name": "Recursive", "value": True, "advanced": True}, "use_multithreading": { "display_name": "Use Multithreading", - "value": True, "advanced": True, }, } @@ -61,7 +70,9 @@ class GatherRecordsComponent(CustomComponent): glob = "**/*" if recursive else "*" paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob) - file_paths = [str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)] + file_paths = [ + str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p) + ] return file_paths @@ -91,13 +102,20 @@ class GatherRecordsComponent(CustomComponent): use_multithreading: bool, ) -> List[Record]: if use_multithreading: - records = self.parallel_load_records(file_paths, silent_errors, max_concurrency) + records = self.parallel_load_records( + file_paths, silent_errors, max_concurrency + ) else: - records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths] + records = [ + self.parse_file_to_record(file_path, silent_errors) + for file_path in file_paths + ] records = list(filter(None, records)) return records - def parallel_load_records(self, file_paths: List[str], silent_errors: bool, max_concurrency: int) -> List[Record]: + def parallel_load_records( + self, file_paths: List[str], silent_errors: bool, max_concurrency: int + ) -> List[Record]: with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor: loaded_files = executor.map( lambda file_path: self.parse_file_to_record(file_path, silent_errors), @@ -108,7 +126,7 @@ class GatherRecordsComponent(CustomComponent): def build( self, path: str, - types: List[str] = None, + types: Optional[List[str]] = None, depth: int = 0, max_concurrency: int = 2, load_hidden: bool = False, @@ -116,14 +134,23 @@ class GatherRecordsComponent(CustomComponent): silent_errors: bool = False, use_multithreading: bool = True, ) -> List[Record]: + if types is None: + types = [] resolved_path = self.resolve_path(path) - file_paths = self.retrieve_file_paths(resolved_path, types, load_hidden, recursive, depth) + file_paths = self.retrieve_file_paths( + resolved_path, types, load_hidden, recursive, depth + ) loaded_records = [] if use_multithreading: - loaded_records = self.parallel_load_records(file_paths, silent_errors, max_concurrency) + loaded_records = self.parallel_load_records( + file_paths, silent_errors, max_concurrency + ) else: - loaded_records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths] + loaded_records = [ + self.parse_file_to_record(file_path, silent_errors) + for file_path in file_paths + ] loaded_records = list(filter(None, loaded_records)) self.status = loaded_records return loaded_records diff --git a/src/backend/langflow/components/embeddings/HuggingFaceEmbeddings.py b/src/backend/langflow/components/embeddings/HuggingFaceEmbeddings.py index 6f3540358..4b25dffe7 100644 --- a/src/backend/langflow/components/embeddings/HuggingFaceEmbeddings.py +++ b/src/backend/langflow/components/embeddings/HuggingFaceEmbeddings.py @@ -9,6 +9,7 @@ class HuggingFaceEmbeddingsComponent(CustomComponent): documentation = ( "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers" ) + icon="HuggingFace" def build_config(self): return { diff --git a/src/backend/langflow/components/embeddings/HuggingFaceInferenceAPIEmbeddings.py b/src/backend/langflow/components/embeddings/HuggingFaceInferenceAPIEmbeddings.py index acc828d7f..edb83ed72 100644 --- a/src/backend/langflow/components/embeddings/HuggingFaceInferenceAPIEmbeddings.py +++ b/src/backend/langflow/components/embeddings/HuggingFaceInferenceAPIEmbeddings.py @@ -9,6 +9,8 @@ class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent): display_name = "HuggingFaceInferenceAPIEmbeddings" description = "HuggingFace sentence_transformers embedding models, API version." documentation = "https://github.com/huggingface/text-embeddings-inference" + icon="HuggingFace" + def build_config(self): return { diff --git a/src/backend/langflow/components/io/ChatInput.py b/src/backend/langflow/components/io/ChatInput.py index 653054e0a..de8ce14cb 100644 --- a/src/backend/langflow/components/io/ChatInput.py +++ b/src/backend/langflow/components/io/ChatInput.py @@ -1,7 +1,7 @@ from typing import Optional, Union +from langflow.components.io.base.chat import ChatComponent from langflow.field_typing import Text -from langflow.io.schema import ChatComponent from langflow.schema import Record diff --git a/src/backend/langflow/components/io/ChatOutput.py b/src/backend/langflow/components/io/ChatOutput.py index 842a083fb..0cd51f663 100644 --- a/src/backend/langflow/components/io/ChatOutput.py +++ b/src/backend/langflow/components/io/ChatOutput.py @@ -1,7 +1,7 @@ from typing import Optional, Union +from langflow.components.io.base.chat import ChatComponent from langflow.field_typing import Text -from langflow.io.schema import ChatComponent from langflow.schema import Record diff --git a/src/backend/langflow/components/io/MessageHistory.py b/src/backend/langflow/components/io/MessageHistory.py index 345de4ba0..e9c3f1a87 100644 --- a/src/backend/langflow/components/io/MessageHistory.py +++ b/src/backend/langflow/components/io/MessageHistory.py @@ -12,7 +12,7 @@ class MessageHistoryComponent(CustomComponent): def build_config(self): return { "sender": { - "options": ["Machine", "User"], + "options": ["Machine", "User", "Machine and User"], "display_name": "Sender Type", }, "sender_name": {"display_name": "Sender Name"}, @@ -38,6 +38,8 @@ class MessageHistoryComponent(CustomComponent): session_id: Optional[str] = None, n_messages: int = 5, ) -> List[Record]: + if sender == "Machine and User": + sender = None messages = get_messages( sender=sender, sender_name=sender_name, diff --git a/src/backend/langflow/components/io/TextInput.py b/src/backend/langflow/components/io/TextInput.py index 3fba54fda..f8c1ad606 100644 --- a/src/backend/langflow/components/io/TextInput.py +++ b/src/backend/langflow/components/io/TextInput.py @@ -9,11 +9,11 @@ class TextInput(CustomComponent): description = "Used to pass text input to the next component." field_config = { - "value": {"display_name": "Value", "multiline": True}, + "input_value": {"display_name": "Value", "multiline": True}, } - def build(self, value: Optional[str] = "") -> Text: - self.status = value - if not value: - value = "" - return value + def build(self, input_value: Optional[str] = "") -> Text: + self.status = input_value + if not input_value: + input_value = "" + return input_value diff --git a/src/backend/langflow/io/__init__.py b/src/backend/langflow/components/io/base/__init__.py similarity index 100% rename from src/backend/langflow/io/__init__.py rename to src/backend/langflow/components/io/base/__init__.py diff --git a/src/backend/langflow/io/schema.py b/src/backend/langflow/components/io/base/chat.py similarity index 100% rename from src/backend/langflow/io/schema.py rename to src/backend/langflow/components/io/base/chat.py diff --git a/src/backend/langflow/components/model_specs/AmazonBedrockSpecs.py b/src/backend/langflow/components/model_specs/AmazonBedrockSpecs.py index 0c1e3b159..3b79ff76c 100644 --- a/src/backend/langflow/components/model_specs/AmazonBedrockSpecs.py +++ b/src/backend/langflow/components/model_specs/AmazonBedrockSpecs.py @@ -10,6 +10,8 @@ from langflow import CustomComponent class AmazonBedrockComponent(CustomComponent): display_name: str = "Amazon Bedrock" description: str = "LLM model from Amazon Bedrock." + icon = "Amazon" + def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/AnthropicLLMSpecs.py b/src/backend/langflow/components/model_specs/AnthropicLLMSpecs.py index 94c4ed8f5..121c5b2c2 100644 --- a/src/backend/langflow/components/model_specs/AnthropicLLMSpecs.py +++ b/src/backend/langflow/components/model_specs/AnthropicLLMSpecs.py @@ -10,6 +10,7 @@ from langflow import CustomComponent class AnthropicLLM(CustomComponent): display_name: str = "AnthropicLLM" description: str = "Anthropic Chat&Completion large language models." + icon ="Anthropic" def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/AnthropicSpecs.py b/src/backend/langflow/components/model_specs/AnthropicSpecs.py index c1b776617..7a0f3e4db 100644 --- a/src/backend/langflow/components/model_specs/AnthropicSpecs.py +++ b/src/backend/langflow/components/model_specs/AnthropicSpecs.py @@ -10,6 +10,8 @@ from langflow.field_typing import BaseLanguageModel, NestedDict class AnthropicComponent(CustomComponent): display_name = "Anthropic" description = "Anthropic large language models." + icon ="Anthropic" + def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/ChatAnthropicSpecs.py b/src/backend/langflow/components/model_specs/ChatAnthropicSpecs.py index c0e376d16..9534c7cf1 100644 --- a/src/backend/langflow/components/model_specs/ChatAnthropicSpecs.py +++ b/src/backend/langflow/components/model_specs/ChatAnthropicSpecs.py @@ -9,6 +9,7 @@ class ChatAnthropicComponent(CustomComponent): display_name = "ChatAnthropic" description = "`Anthropic` chat large language models." documentation = "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic" + icon ="Anthropic" def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/ChatOpenAISpecs.py b/src/backend/langflow/components/model_specs/ChatOpenAISpecs.py index 2ad4f0309..0f20f0852 100644 --- a/src/backend/langflow/components/model_specs/ChatOpenAISpecs.py +++ b/src/backend/langflow/components/model_specs/ChatOpenAISpecs.py @@ -9,6 +9,7 @@ from langflow.field_typing import BaseLanguageModel, NestedDict class ChatOpenAIComponent(CustomComponent): display_name = "ChatOpenAI" description = "`OpenAI` Chat large language models API." + icon = "OpenAI" def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/ChatVertexAISpecs.py b/src/backend/langflow/components/model_specs/ChatVertexAISpecs.py index 66235a1d8..8cc7b4256 100644 --- a/src/backend/langflow/components/model_specs/ChatVertexAISpecs.py +++ b/src/backend/langflow/components/model_specs/ChatVertexAISpecs.py @@ -10,6 +10,8 @@ from langflow.field_typing import BaseLanguageModel class ChatVertexAIComponent(CustomComponent): display_name = "ChatVertexAI" description = "`Vertex AI` Chat large language models API." + icon="VertexAI" + def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/CohereSpecs.py b/src/backend/langflow/components/model_specs/CohereSpecs.py index 3b74fc9b4..8cb0f3624 100644 --- a/src/backend/langflow/components/model_specs/CohereSpecs.py +++ b/src/backend/langflow/components/model_specs/CohereSpecs.py @@ -7,6 +7,7 @@ class CohereComponent(CustomComponent): display_name = "Cohere" description = "Cohere large language models." documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere" + icon = "Cohere" def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/GoogleGenerativeAISpecs.py b/src/backend/langflow/components/model_specs/GoogleGenerativeAISpecs.py index eb1134583..9ff17e389 100644 --- a/src/backend/langflow/components/model_specs/GoogleGenerativeAISpecs.py +++ b/src/backend/langflow/components/model_specs/GoogleGenerativeAISpecs.py @@ -10,6 +10,7 @@ class GoogleGenerativeAIComponent(CustomComponent): display_name: str = "Google Generative AI" description: str = "A component that uses Google Generative AI to generate text." documentation: str = "http://docs.langflow.org/components/custom" + icon = "Google" def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/HuggingFaceEndpointsSpecs.py b/src/backend/langflow/components/model_specs/HuggingFaceEndpointsSpecs.py index 579e3c0b3..704de9029 100644 --- a/src/backend/langflow/components/model_specs/HuggingFaceEndpointsSpecs.py +++ b/src/backend/langflow/components/model_specs/HuggingFaceEndpointsSpecs.py @@ -8,6 +8,8 @@ from langflow import CustomComponent class HuggingFaceEndpointsComponent(CustomComponent): display_name: str = "Hugging Face Inference API" description: str = "LLM model from Hugging Face Inference API." + icon="HuggingFace" + def build_config(self): return { diff --git a/src/backend/langflow/components/model_specs/VertexAISpecs.py b/src/backend/langflow/components/model_specs/VertexAISpecs.py index 008451bc8..a6013e17d 100644 --- a/src/backend/langflow/components/model_specs/VertexAISpecs.py +++ b/src/backend/langflow/components/model_specs/VertexAISpecs.py @@ -7,6 +7,7 @@ from langchain_community.llms.vertexai import VertexAI class VertexAIComponent(CustomComponent): display_name = "VertexAI" description = "Google Vertex AI large language models" + icon="VertexAI" def build_config(self): return { diff --git a/src/backend/langflow/components/models/AmazonBedrockModel.py b/src/backend/langflow/components/models/AmazonBedrockModel.py index 478bf8e9a..fdddc07d1 100644 --- a/src/backend/langflow/components/models/AmazonBedrockModel.py +++ b/src/backend/langflow/components/models/AmazonBedrockModel.py @@ -2,13 +2,14 @@ from typing import Optional from langchain_community.chat_models.bedrock import BedrockChat -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class AmazonBedrockComponent(CustomComponent): +class AmazonBedrockComponent(LCModelComponent): display_name: str = "Amazon Bedrock Model" description: str = "Generate text using LLM model from Amazon Bedrock." + icon = "Amazon" def build_config(self): return { @@ -34,7 +35,11 @@ class AmazonBedrockComponent(CustomComponent): "model_kwargs": {"display_name": "Model Kwargs"}, "cache": {"display_name": "Cache"}, "code": {"advanced": True}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -47,6 +52,7 @@ class AmazonBedrockComponent(CustomComponent): endpoint_url: Optional[str] = None, streaming: bool = False, cache: Optional[bool] = None, + stream: bool = False, ) -> Text: try: output = BedrockChat( @@ -60,7 +66,5 @@ class AmazonBedrockComponent(CustomComponent): ) # type: ignore except Exception as e: raise ValueError("Could not connect to AmazonBedrock API.") from e - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/AnthropicModel.py b/src/backend/langflow/components/models/AnthropicModel.py index cb8e55194..a3ba510a4 100644 --- a/src/backend/langflow/components/models/AnthropicModel.py +++ b/src/backend/langflow/components/models/AnthropicModel.py @@ -3,15 +3,16 @@ from typing import Optional from langchain_community.chat_models.anthropic import ChatAnthropic from pydantic.v1 import SecretStr -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class AnthropicLLM(CustomComponent): +class AnthropicLLM(LCModelComponent): display_name: str = "AnthropicModel" description: str = ( "Generate text using Anthropic Chat&Completion large language models." ) + icon = "Anthropic" def build_config(self): return { @@ -49,7 +50,11 @@ class AnthropicLLM(CustomComponent): "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.", }, "code": {"show": False}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -60,6 +65,7 @@ class AnthropicLLM(CustomComponent): max_tokens: Optional[int] = None, temperature: Optional[float] = None, api_endpoint: Optional[str] = None, + stream: bool = False, ) -> Text: # Set default API endpoint if not provided if not api_endpoint: @@ -77,7 +83,5 @@ class AnthropicLLM(CustomComponent): ) except Exception as e: raise ValueError("Could not connect to Anthropic API.") from e - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/AzureOpenAIModel.py b/src/backend/langflow/components/models/AzureOpenAIModel.py index df8413870..392f390c4 100644 --- a/src/backend/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/langflow/components/models/AzureOpenAIModel.py @@ -3,16 +3,17 @@ from typing import Optional from langchain.llms.base import BaseLanguageModel from langchain_openai import AzureChatOpenAI -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent -class AzureChatOpenAIComponent(CustomComponent): +class AzureChatOpenAIComponent(LCModelComponent): display_name: str = "AzureOpenAI Model" description: str = "Generate text using LLM model from Azure OpenAI." documentation: str = ( "https://python.langchain.com/docs/integrations/llms/azure_openai" ) beta = False + icon = "Azure" AZURE_OPENAI_MODELS = [ "gpt-35-turbo", @@ -73,7 +74,11 @@ class AzureChatOpenAIComponent(CustomComponent): "info": "Maximum number of tokens to generate.", }, "code": {"show": False}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -86,6 +91,7 @@ class AzureChatOpenAIComponent(CustomComponent): api_version: str, temperature: float = 0.7, max_tokens: Optional[int] = 1000, + stream: bool = False, ) -> BaseLanguageModel: try: output = AzureChatOpenAI( @@ -99,7 +105,5 @@ class AzureChatOpenAIComponent(CustomComponent): ) except Exception as e: raise ValueError("Could not connect to AzureOpenAI API.") from e - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/BaiduQianfanChatModel.py b/src/backend/langflow/components/models/BaiduQianfanChatModel.py index 0075316a2..f0815603f 100644 --- a/src/backend/langflow/components/models/BaiduQianfanChatModel.py +++ b/src/backend/langflow/components/models/BaiduQianfanChatModel.py @@ -3,16 +3,17 @@ from typing import Optional from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint from pydantic.v1 import SecretStr -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class QianfanChatEndpointComponent(CustomComponent): +class QianfanChatEndpointComponent(LCModelComponent): display_name: str = "QianfanChat Model" description: str = ( "Generate text using Baidu Qianfan chat models. Get more detail from " "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint." ) + icon = "BaiduQianfan" def build_config(self): return { @@ -68,7 +69,11 @@ class QianfanChatEndpointComponent(CustomComponent): "info": "Endpoint of the Qianfan LLM, required if custom model used.", }, "code": {"show": False}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -81,6 +86,7 @@ class QianfanChatEndpointComponent(CustomComponent): temperature: Optional[float] = None, penalty_score: Optional[float] = None, endpoint: Optional[str] = None, + stream: bool = False, ) -> Text: try: output = QianfanChatEndpoint( # type: ignore @@ -94,7 +100,5 @@ class QianfanChatEndpointComponent(CustomComponent): ) except Exception as e: raise ValueError("Could not connect to Baidu Qianfan API.") from e - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/CTransformersModel.py b/src/backend/langflow/components/models/CTransformersModel.py index fb292d9cf..31123ad7e 100644 --- a/src/backend/langflow/components/models/CTransformersModel.py +++ b/src/backend/langflow/components/models/CTransformersModel.py @@ -2,11 +2,11 @@ from typing import Dict, Optional from langchain_community.llms.ctransformers import CTransformers -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class CTransformersComponent(CustomComponent): +class CTransformersComponent(LCModelComponent): display_name = "CTransformersModel" description = "Generate text using CTransformers LLM models" documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/ctransformers" @@ -28,7 +28,11 @@ class CTransformersComponent(CustomComponent): "field_type": "dict", "value": '{"top_k":40,"top_p":0.95,"temperature":0.8,"repetition_penalty":1.1,"last_n_tokens":64,"seed":-1,"max_new_tokens":256,"stop":"","stream":"False","reset":"True","batch_size":8,"threads":-1,"context_length":-1,"gpu_layers":0}', }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -38,11 +42,10 @@ class CTransformersComponent(CustomComponent): input_value: str, model_type: str, config: Optional[Dict] = None, + stream: Optional[bool] = False, ) -> Text: output = CTransformers( model=model, model_file=model_file, model_type=model_type, config=config ) - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/CohereModel.py b/src/backend/langflow/components/models/CohereModel.py index c2a004c38..a32fb9b4b 100644 --- a/src/backend/langflow/components/models/CohereModel.py +++ b/src/backend/langflow/components/models/CohereModel.py @@ -1,14 +1,16 @@ from langchain_community.chat_models.cohere import ChatCohere -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class CohereComponent(CustomComponent): +class CohereComponent(LCModelComponent): display_name = "CohereModel" description = "Generate text using Cohere large language models." documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere" + icon = "Cohere" + def build_config(self): return { "cohere_api_key": { @@ -28,7 +30,11 @@ class CohereComponent(CustomComponent): "type": "float", "show": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -37,14 +43,11 @@ class CohereComponent(CustomComponent): input_value: str, max_tokens: int = 256, temperature: float = 0.75, + stream: bool = False, ) -> Text: output = ChatCohere( cohere_api_key=cohere_api_key, max_tokens=max_tokens, temperature=temperature, ) - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result - return result + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py index 3b0c758e8..50a3f0f7a 100644 --- a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py +++ b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py @@ -1,16 +1,17 @@ from typing import Optional -from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore -from pydantic.v1.types import SecretStr +from langchain_google_genai import ChatGoogleGenerativeAI +from pydantic.v1 import SecretStr -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import RangeSpec, Text -class GoogleGenerativeAIComponent(CustomComponent): +class GoogleGenerativeAIComponent(LCModelComponent): display_name: str = "Google Generative AIModel" description: str = "Generate text using Google Generative AI to generate text." - documentation: str = "http://docs.langflow.org/components/custom" + icon = "GoogleGenerativeAI" + icon = "Google" def build_config(self): return { @@ -50,7 +51,11 @@ class GoogleGenerativeAIComponent(CustomComponent): "code": { "advanced": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input", "info": "The input to the model."}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -63,6 +68,7 @@ class GoogleGenerativeAIComponent(CustomComponent): top_k: Optional[int] = None, top_p: Optional[float] = None, n: Optional[int] = 1, + stream: bool = False, ) -> Text: output = ChatGoogleGenerativeAI( model=model, @@ -73,7 +79,4 @@ class GoogleGenerativeAIComponent(CustomComponent): n=n or 1, google_api_key=SecretStr(google_api_key), ) - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/HuggingFaceModel.py b/src/backend/langflow/components/models/HuggingFaceModel.py index 99fcc6ab0..3d92272e6 100644 --- a/src/backend/langflow/components/models/HuggingFaceModel.py +++ b/src/backend/langflow/components/models/HuggingFaceModel.py @@ -3,13 +3,14 @@ from typing import Optional from langchain_community.chat_models.huggingface import ChatHuggingFace from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class HuggingFaceEndpointsComponent(CustomComponent): +class HuggingFaceEndpointsComponent(LCModelComponent): display_name: str = "Hugging Face Inference API models" description: str = "Generate text using LLM model from Hugging Face Inference API." + icon = "HuggingFace" def build_config(self): return { @@ -24,7 +25,11 @@ class HuggingFaceEndpointsComponent(CustomComponent): "field_type": "code", }, "code": {"show": False}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -34,6 +39,7 @@ class HuggingFaceEndpointsComponent(CustomComponent): task: str = "text2text-generation", huggingfacehub_api_token: Optional[str] = None, model_kwargs: Optional[dict] = None, + stream: bool = False, ) -> Text: try: llm = HuggingFaceEndpoint( @@ -45,7 +51,4 @@ class HuggingFaceEndpointsComponent(CustomComponent): except Exception as e: raise ValueError("Could not connect to HuggingFace Endpoints API.") from e output = ChatHuggingFace(llm=llm) - message = output.invoke(input_value)alue) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/LlamaCppModel.py b/src/backend/langflow/components/models/LlamaCppModel.py index 5941f213b..1ca2cd3c1 100644 --- a/src/backend/langflow/components/models/LlamaCppModel.py +++ b/src/backend/langflow/components/models/LlamaCppModel.py @@ -2,11 +2,11 @@ from typing import Any, Dict, List, Optional from langchain_community.llms.llamacpp import LlamaCpp -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class LlamaCppComponent(CustomComponent): +class LlamaCppComponent(LCModelComponent): display_name = "LlamaCppModel" description = "Generate text using llama.cpp model." documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/llamacpp" @@ -56,7 +56,11 @@ class LlamaCppComponent(CustomComponent): "use_mmap": {"display_name": "Use Mmap", "advanced": True}, "verbose": {"display_name": "Verbose", "advanced": True}, "vocab_only": {"display_name": "Vocab Only", "advanced": True}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -97,6 +101,7 @@ class LlamaCppComponent(CustomComponent): use_mmap: Optional[bool] = True, verbose: bool = True, vocab_only: bool = False, + stream: bool = False, ) -> Text: output = LlamaCpp( model_path=model_path, @@ -135,9 +140,5 @@ class LlamaCppComponent(CustomComponent): verbose=verbose, vocab_only=vocab_only, ) - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/OllamaModel.py b/src/backend/langflow/components/models/OllamaModel.py index 7ae896532..7929c2b43 100644 --- a/src/backend/langflow/components/models/OllamaModel.py +++ b/src/backend/langflow/components/models/OllamaModel.py @@ -3,17 +3,19 @@ from typing import Any, Dict, List, Optional # from langchain_community.chat_models import ChatOllama from langchain_community.chat_models import ChatOllama +from langflow.components.models.base.model import LCModelComponent + # from langchain.chat_models import ChatOllama -from langflow import CustomComponent from langflow.field_typing import Text # whe When a callback component is added to Langflow, the comment must be uncommented. # from langchain.callbacks.manager import CallbackManager -class ChatOllamaComponent(CustomComponent): +class ChatOllamaComponent(LCModelComponent): display_name = "ChatOllamaModel" description = "Generate text using Local LLM for chat with Ollama." + icon = "Ollama" def build_config(self) -> dict: return { @@ -164,7 +166,11 @@ class ChatOllamaComponent(CustomComponent): "info": "Template to use for generating text.", "advanced": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -197,6 +203,7 @@ class ChatOllamaComponent(CustomComponent): timeout: Optional[int] = None, top_k: Optional[int] = None, top_p: Optional[int] = None, + stream: Optional[bool] = False, ) -> Text: if not base_url: base_url = "http://localhost:11434" @@ -250,7 +257,5 @@ class ChatOllamaComponent(CustomComponent): output = ChatOllama(**llm_params) # type: ignore except Exception as e: raise ValueError("Could not initialize Ollama LLM.") from e - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/OpenAIModel.py b/src/backend/langflow/components/models/OpenAIModel.py index ee6809e0b..7a28acee6 100644 --- a/src/backend/langflow/components/models/OpenAIModel.py +++ b/src/backend/langflow/components/models/OpenAIModel.py @@ -2,17 +2,18 @@ from typing import Optional from langchain_openai import ChatOpenAI -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import NestedDict, Text -class OpenAIModelComponent(CustomComponent): +class OpenAIModelComponent(LCModelComponent): display_name = "OpenAI Model" description = "Generates text using OpenAI's models." + icon = "OpenAI" def build_config(self): return { - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, "max_tokens": { "display_name": "Max Tokens", "advanced": False, @@ -57,6 +58,10 @@ class OpenAIModelComponent(CustomComponent): "required": False, "value": 0.7, }, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -68,10 +73,11 @@ class OpenAIModelComponent(CustomComponent): openai_api_base: Optional[str] = None, openai_api_key: Optional[str] = None, temperature: float = 0.7, + stream: Optional[bool] = False, ) -> Text: if not openai_api_base: openai_api_base = "https://api.openai.com/v1" - model = ChatOpenAI( + output = ChatOpenAI( max_tokens=max_tokens, model_kwargs=model_kwargs, model=model_name, @@ -80,7 +86,4 @@ class OpenAIModelComponent(CustomComponent): temperature=temperature, ) - message = model.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/VertexAiModel.py b/src/backend/langflow/components/models/VertexAiModel.py index c05dc5e94..5a1950f39 100644 --- a/src/backend/langflow/components/models/VertexAiModel.py +++ b/src/backend/langflow/components/models/VertexAiModel.py @@ -2,13 +2,15 @@ from typing import List, Optional from langchain_core.messages.base import BaseMessage -from langflow import CustomComponent +from langflow.components.models.base.model import LCModelComponent from langflow.field_typing import Text -class ChatVertexAIComponent(CustomComponent): +class ChatVertexAIComponent(LCModelComponent): display_name = "ChatVertexAIModel" description = "Generate text using Vertex AI Chat large language models API." + icon="VertexAI" + def build_config(self): return { @@ -57,7 +59,11 @@ class ChatVertexAIComponent(CustomComponent): "value": False, "advanced": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -73,6 +79,7 @@ class ChatVertexAIComponent(CustomComponent): top_k: int = 40, top_p: float = 0.95, verbose: bool = False, + stream: bool = False, ) -> Text: try: from langchain_google_vertexai import ChatVertexAI @@ -92,7 +99,5 @@ class ChatVertexAIComponent(CustomComponent): top_p=top_p, verbose=verbose, ) - message = output.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result + + return self.get_result(output=output, stream=stream, input_value=input_value) diff --git a/src/backend/langflow/components/models/base/__init__.py b/src/backend/langflow/components/models/base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/langflow/components/models/base/model.py b/src/backend/langflow/components/models/base/model.py new file mode 100644 index 000000000..9f9ca7b36 --- /dev/null +++ b/src/backend/langflow/components/models/base/model.py @@ -0,0 +1,28 @@ +from langchain_core.runnables import Runnable + +from langflow import CustomComponent + + +class LCModelComponent(CustomComponent): + display_name: str = "Model Name" + description: str = "Model Description" + + def get_result(self, output: Runnable, 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 = output.stream(input_value) + else: + message = output.invoke(input_value) + result = message.content if hasattr(message, "content") else message + self.status = result + return result diff --git a/src/backend/langflow/components/retrievers/AmazonKendra.py b/src/backend/langflow/components/retrievers/AmazonKendra.py index c0aedb1bc..886afeff5 100644 --- a/src/backend/langflow/components/retrievers/AmazonKendra.py +++ b/src/backend/langflow/components/retrievers/AmazonKendra.py @@ -9,6 +9,7 @@ from langflow import CustomComponent class AmazonKendraRetrieverComponent(CustomComponent): display_name: str = "Amazon Kendra Retriever" description: str = "Retriever that uses the Amazon Kendra API." + icon = "Amazon" def build_config(self): return { diff --git a/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py b/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py index 26afd765c..aa4f412a8 100644 --- a/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py +++ b/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py @@ -17,6 +17,8 @@ class VectaraSelfQueryRetriverComponent(CustomComponent): description: str = "Implementation of Vectara Self Query Retriever" documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query" beta = True + icon="Vectara" + field_config = { "code": {"show": True}, diff --git a/src/backend/langflow/components/utilities/RunnableExecutor.py b/src/backend/langflow/components/utilities/RunnableExecutor.py index 5533e6d1d..502e1eec6 100644 --- a/src/backend/langflow/components/utilities/RunnableExecutor.py +++ b/src/backend/langflow/components/utilities/RunnableExecutor.py @@ -15,7 +15,7 @@ class RunnableExecComponent(CustomComponent): "display_name": "Input Key", "info": "The key to use for the input.", }, - "inputs": { + "input_value": { "display_name": "Inputs", "info": "The inputs to pass to the runnable.", }, diff --git a/src/backend/langflow/components/vectorstores/ChromaSearch.py b/src/backend/langflow/components/vectorstores/ChromaSearch.py index 5dd33abf2..e3f37108c 100644 --- a/src/backend/langflow/components/vectorstores/ChromaSearch.py +++ b/src/backend/langflow/components/vectorstores/ChromaSearch.py @@ -3,12 +3,12 @@ from typing import List, Optional import chromadb # type: ignore from langchain_community.vectorstores.chroma import Chroma -from langflow import CustomComponent +from langflow.components.vectorstores.base.model import LCVectorStoreComponent from langflow.field_typing import Embeddings, Text -from langflow.schema import Record, docs_to_records +from langflow.schema import Record -class ChromaSearchComponent(CustomComponent): +class ChromaSearchComponent(LCVectorStoreComponent): """ A custom component for implementing a Vector Store using Chroma. """ @@ -26,7 +26,7 @@ class ChromaSearchComponent(CustomComponent): - dict: A dictionary containing the configuration options for the component. """ return { - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, "search_type": { "display_name": "Search Type", "options": ["Similarity", "MMR"], @@ -101,17 +101,11 @@ class ChromaSearchComponent(CustomComponent): chroma_server_ssl_enabled=chroma_server_ssl_enabled, ) index_directory = self.resolve_path(index_directory) - chroma = Chroma( + vector_store = Chroma( embedding_function=embedding, collection_name=collection_name, persist_directory=index_directory, client_settings=chroma_settings, ) - # Validate the inputs - docs = [] - if inputs and isinstance(inputs, str): - docs = chroma.search(query=inputs, search_type=search_type.lower()) - else: - raise ValueError("Invalid inputs provided.") - return docs_to_records(docs) + return self.search_with_vector_store(input_value, search_type, vector_store) diff --git a/src/backend/langflow/components/vectorstores/FAISS.py b/src/backend/langflow/components/vectorstores/FAISS.py index dec14f6db..0cecab8e7 100644 --- a/src/backend/langflow/components/vectorstores/FAISS.py +++ b/src/backend/langflow/components/vectorstores/FAISS.py @@ -3,24 +3,36 @@ from typing import List, Union from langchain.schema import BaseRetriever from langchain_community.vectorstores import VectorStore from langchain_community.vectorstores.faiss import FAISS + from langflow import CustomComponent from langflow.field_typing import Document, Embeddings class FAISSComponent(CustomComponent): display_name = "FAISS" - description = "Construct FAISS wrapper from raw documents." + description = "Ingest documents into FAISS Vector Store." documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss" def build_config(self): return { "documents": {"display_name": "Documents"}, "embedding": {"display_name": "Embedding"}, + "folder_path": { + "display_name": "Folder Path", + "info": "Path to save the FAISS index. It will be relative to where Langflow is running.", + }, + "index_name": {"display_name": "Index Name"}, } def build( self, embedding: Embeddings, documents: List[Document], + folder_path: str, + index_name: str = "langflow_index", ) -> Union[VectorStore, FAISS, BaseRetriever]: - return FAISS.from_documents(documents=documents, embedding=embedding) + vector_store = FAISS.from_documents(documents=documents, embedding=embedding) + if not folder_path: + raise ValueError("Folder path is required to save the FAISS index.") + path = self.resolve_path(folder_path) + vector_store.save_local(str(path), index_name) diff --git a/src/backend/langflow/components/vectorstores/FAISSSearch.py b/src/backend/langflow/components/vectorstores/FAISSSearch.py new file mode 100644 index 000000000..dbc63faac --- /dev/null +++ b/src/backend/langflow/components/vectorstores/FAISSSearch.py @@ -0,0 +1,45 @@ +from typing import List + +from langchain_community.vectorstores.faiss import FAISS + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.field_typing import Embeddings +from langflow.schema import Record + + +class FAISSSearchComponent(LCVectorStoreComponent): + display_name = "FAISS Search" + description = "Search a FAISS Vector Store for similar documents." + documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss" + + def build_config(self): + return { + "documents": {"display_name": "Documents"}, + "embedding": {"display_name": "Embedding"}, + "folder_path": { + "display_name": "Folder Path", + "info": "Path to save the FAISS index. It will be relative to where Langflow is running.", + }, + "input_value": {"display_name": "Input"}, + "index_name": {"display_name": "Index Name"}, + } + + def build( + self, + input_value: str, + embedding: Embeddings, + folder_path: str, + index_name: str = "langflow_index", + ) -> List[Record]: + if not folder_path: + raise ValueError("Folder path is required to save the FAISS index.") + path = self.resolve_path(folder_path) + vector_store = FAISS.load_local( + folder_path=str(path), embeddings=embedding, index_name=index_name + ) + if not vector_store: + raise ValueError("Failed to load the FAISS index.") + + return self.search_with_vector_store( + vector_store=vector_store, input_value=input_value, search_type="similarity" + ) diff --git a/src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py b/src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py new file mode 100644 index 000000000..5d4537408 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py @@ -0,0 +1,57 @@ +from typing import List, Optional + +from langchain_community.vectorstores.mongodb_atlas import MongoDBAtlasVectorSearch + +from langflow import CustomComponent +from langflow.field_typing import Document, Embeddings, NestedDict + + +class MongoDBAtlasComponent(CustomComponent): + display_name = "MongoDB Atlas" + description = ( + "Construct a `MongoDB Atlas Vector Search` vector store from raw documents." + ) + icon="MongoDB" + + def build_config(self): + return { + "documents": {"display_name": "Documents"}, + "embedding": {"display_name": "Embedding"}, + "collection_name": {"display_name": "Collection Name"}, + "db_name": {"display_name": "Database Name"}, + "index_name": {"display_name": "Index Name"}, + "mongodb_atlas_cluster_uri": {"display_name": "MongoDB Atlas Cluster URI"}, + "search_kwargs": {"display_name": "Search Kwargs", "advanced": True}, + } + + def build( + self, + embedding: Embeddings, + documents: List[Document] = None, + collection_name: str = "", + db_name: str = "", + index_name: str = "", + mongodb_atlas_cluster_uri: str = "", + search_kwargs: Optional[NestedDict] = None, + ) -> MongoDBAtlasVectorSearch: + search_kwargs = search_kwargs or {} + if documents: + vector_store = MongoDBAtlasVectorSearch.from_documents( + documents=documents, + embedding=embedding, + collection_name=collection_name, + db_name=db_name, + index_name=index_name, + mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri, + search_kwargs=search_kwargs, + ) + else: + vector_store = MongoDBAtlasVectorSearch( + embedding=embedding, + collection_name=collection_name, + db_name=db_name, + index_name=index_name, + mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri, + search_kwargs=search_kwargs, + ) + return vector_store diff --git a/src/backend/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py b/src/backend/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py index d2d215f2b..6393c2a7b 100644 --- a/src/backend/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py +++ b/src/backend/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py @@ -1,22 +1,22 @@ from typing import List, Optional -from langchain_community.vectorstores import MongoDBAtlasVectorSearch - -from langflow import CustomComponent -from langflow.field_typing import ( - Document, - Embeddings, - NestedDict, -) +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.MongoDBAtlasVector import MongoDBAtlasComponent +from langflow.field_typing import Embeddings, NestedDict +from langflow.schema import Record -class MongoDBAtlasComponent(CustomComponent): - display_name = "MongoDB Atlas" - description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents." +class MongoDBAtlasSearchComponent(MongoDBAtlasComponent, LCVectorStoreComponent): + display_name = "MongoDB Atlas Search" + description = "Search a MongoDB Atlas Vector Store for similar documents." def build_config(self): return { - "documents": {"display_name": "Documents"}, + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, "embedding": {"display_name": "Embedding"}, "collection_name": {"display_name": "Collection Name"}, "db_name": {"display_name": "Database Name"}, @@ -27,17 +27,16 @@ class MongoDBAtlasComponent(CustomComponent): def build( self, - documents: List[Document], + input_value: str, + search_type: str, embedding: Embeddings, collection_name: str = "", db_name: str = "", index_name: str = "", mongodb_atlas_cluster_uri: str = "", search_kwargs: Optional[NestedDict] = None, - ) -> MongoDBAtlasVectorSearch: - search_kwargs = search_kwargs or {} - return MongoDBAtlasVectorSearch( - documents=documents, + ) -> List[Record]: + vector_store = super().build( embedding=embedding, collection_name=collection_name, db_name=db_name, @@ -45,3 +44,8 @@ class MongoDBAtlasComponent(CustomComponent): mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri, search_kwargs=search_kwargs, ) + if not vector_store: + raise ValueError("Failed to create MongoDB Atlas Vector Store") + return self.search_with_vector_store( + vector_store=vector_store, input_value=input_value, search_type=search_type + ) diff --git a/src/backend/langflow/components/vectorstores/Pinecone.py b/src/backend/langflow/components/vectorstores/Pinecone.py index 147af1df8..54222b133 100644 --- a/src/backend/langflow/components/vectorstores/Pinecone.py +++ b/src/backend/langflow/components/vectorstores/Pinecone.py @@ -5,6 +5,7 @@ import pinecone # type: ignore from langchain.schema import BaseRetriever from langchain_community.vectorstores import VectorStore from langchain_community.vectorstores.pinecone import Pinecone + from langflow import CustomComponent from langflow.field_typing import Document, Embeddings @@ -12,6 +13,7 @@ from langflow.field_typing import Document, Embeddings class PineconeComponent(CustomComponent): display_name = "Pinecone" description = "Construct Pinecone wrapper from raw documents." + icon = "Pinecone" def build_config(self): return { @@ -19,10 +21,23 @@ class PineconeComponent(CustomComponent): "embedding": {"display_name": "Embedding"}, "index_name": {"display_name": "Index Name"}, "namespace": {"display_name": "Namespace"}, - "pinecone_api_key": {"display_name": "Pinecone API Key", "default": "", "password": True, "required": True}, - "pinecone_env": {"display_name": "Pinecone Environment", "default": "", "required": True}, + "pinecone_api_key": { + "display_name": "Pinecone API Key", + "default": "", + "password": True, + "required": True, + }, + "pinecone_env": { + "display_name": "Pinecone Environment", + "default": "", + "required": True, + }, "search_kwargs": {"display_name": "Search Kwargs", "default": "{}"}, - "pool_threads": {"display_name": "Pool Threads", "default": 1, "advanced": True}, + "pool_threads": { + "display_name": "Pool Threads", + "default": 1, + "advanced": True, + }, } def build( diff --git a/src/backend/langflow/components/vectorstores/PineconeSearch.py b/src/backend/langflow/components/vectorstores/PineconeSearch.py new file mode 100644 index 000000000..7af7f627f --- /dev/null +++ b/src/backend/langflow/components/vectorstores/PineconeSearch.py @@ -0,0 +1,70 @@ +from typing import List, Optional + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.Pinecone import PineconeComponent +from langflow.field_typing import Embeddings +from langflow.schema import Record + + +class PineconeSearchComponent(PineconeComponent, LCVectorStoreComponent): + display_name = "Pinecone Search" + description = "Search a Pinecone Vector Store for similar documents." + icon = "Pinecone" + + def build_config(self): + return { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, + "embedding": {"display_name": "Embedding"}, + "index_name": {"display_name": "Index Name"}, + "namespace": {"display_name": "Namespace"}, + "pinecone_api_key": { + "display_name": "Pinecone API Key", + "default": "", + "password": True, + "required": True, + }, + "pinecone_env": { + "display_name": "Pinecone Environment", + "default": "", + "required": True, + }, + "search_kwargs": {"display_name": "Search Kwargs", "default": "{}"}, + "pool_threads": { + "display_name": "Pool Threads", + "default": 1, + "advanced": True, + }, + } + + def build( + self, + input_value: str, + embedding: Embeddings, + pinecone_env: str, + text_key: str = "text", + pool_threads: int = 4, + index_name: Optional[str] = None, + pinecone_api_key: Optional[str] = None, + namespace: Optional[str] = "default", + search_type: str = "similarity", + ) -> List[Record]: + vector_store = super().build( + embedding=embedding, + pinecone_env=pinecone_env, + documents=[], + text_key=text_key, + pool_threads=pool_threads, + index_name=index_name, + pinecone_api_key=pinecone_api_key, + namespace=namespace, + ) + if not vector_store: + raise ValueError("Failed to load the Pinecone index.") + + return self.search_with_vector_store( + vector_store=vector_store, input_value=input_value, search_type=search_type + ) diff --git a/src/backend/langflow/components/vectorstores/Qdrant.py b/src/backend/langflow/components/vectorstores/Qdrant.py index 6d68cdd55..3541f09e2 100644 --- a/src/backend/langflow/components/vectorstores/Qdrant.py +++ b/src/backend/langflow/components/vectorstores/Qdrant.py @@ -10,6 +10,7 @@ from langflow.field_typing import Document, Embeddings, NestedDict class QdrantComponent(CustomComponent): display_name = "Qdrant" description = "Construct Qdrant wrapper from a list of texts." + icon="Qdrant" def build_config(self): return { diff --git a/src/backend/langflow/components/vectorstores/QdrantSearch.py b/src/backend/langflow/components/vectorstores/QdrantSearch.py new file mode 100644 index 000000000..742690a5d --- /dev/null +++ b/src/backend/langflow/components/vectorstores/QdrantSearch.py @@ -0,0 +1,93 @@ +from typing import List, Optional + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.Qdrant import QdrantComponent +from langflow.field_typing import Embeddings, NestedDict +from langflow.schema import Record + + +class QdrantSearchComponent(QdrantComponent, LCVectorStoreComponent): + display_name = "Qdrant" + description = "Construct Qdrant wrapper from a list of texts." + icon="Qdrant" + + + def build_config(self): + return { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, + "embedding": {"display_name": "Embedding"}, + "api_key": {"display_name": "API Key", "password": True, "advanced": True}, + "collection_name": {"display_name": "Collection Name"}, + "content_payload_key": { + "display_name": "Content Payload Key", + "advanced": True, + }, + "distance_func": {"display_name": "Distance Function", "advanced": True}, + "grpc_port": {"display_name": "gRPC Port", "advanced": True}, + "host": {"display_name": "Host", "advanced": True}, + "https": {"display_name": "HTTPS", "advanced": True}, + "location": {"display_name": "Location", "advanced": True}, + "metadata_payload_key": { + "display_name": "Metadata Payload Key", + "advanced": True, + }, + "path": {"display_name": "Path", "advanced": True}, + "port": {"display_name": "Port", "advanced": True}, + "prefer_grpc": {"display_name": "Prefer gRPC", "advanced": True}, + "prefix": {"display_name": "Prefix", "advanced": True}, + "search_kwargs": {"display_name": "Search Kwargs", "advanced": True}, + "timeout": {"display_name": "Timeout", "advanced": True}, + "url": {"display_name": "URL", "advanced": True}, + } + + def build( + self, + input_value: str, + embedding: Embeddings, + collection_name: str, + search_type: str = "similarity", + api_key: Optional[str] = None, + content_payload_key: str = "page_content", + distance_func: str = "Cosine", + grpc_port: int = 6334, + https: bool = False, + host: Optional[str] = None, + location: Optional[str] = None, + metadata_payload_key: str = "metadata", + path: Optional[str] = None, + port: Optional[int] = 6333, + prefer_grpc: bool = False, + prefix: Optional[str] = None, + search_kwargs: Optional[NestedDict] = None, + timeout: Optional[int] = None, + url: Optional[str] = None, + ) -> List[Record]: + vector_store = super().build( + embedding=embedding, + collection_name=collection_name, + api_key=api_key, + content_payload_key=content_payload_key, + distance_func=distance_func, + grpc_port=grpc_port, + https=https, + host=host, + location=location, + metadata_payload_key=metadata_payload_key, + path=path, + port=port, + prefer_grpc=prefer_grpc, + prefix=prefix, + search_kwargs=search_kwargs, + timeout=timeout, + url=url, + ) + if not vector_store: + raise ValueError("Failed to load the Qdrant index.") + + return self.search_with_vector_store( + vector_store=vector_store, input_value=input_value, search_type=search_type + ) diff --git a/src/backend/langflow/components/vectorstores/RedisSearch.py b/src/backend/langflow/components/vectorstores/RedisSearch.py new file mode 100644 index 000000000..71022de1d --- /dev/null +++ b/src/backend/langflow/components/vectorstores/RedisSearch.py @@ -0,0 +1,77 @@ +from typing import List, Optional + +from langchain.embeddings.base import Embeddings + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.Redis import RedisComponent +from langflow.schema import Record + + +class RedisSearchComponent(RedisComponent, LCVectorStoreComponent): + """ + A custom component for implementing a Vector Store using Redis. + """ + + display_name: str = "Redis Search" + description: str = "Search a Redis Vector Store for similar documents." + documentation = "https://python.langchain.com/docs/integrations/vectorstores/redis" + beta = True + + def build_config(self): + """ + Builds the configuration for the component. + + Returns: + - dict: A dictionary containing the configuration options for the component. + """ + return { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, + "index_name": {"display_name": "Index Name", "value": "your_index"}, + "code": {"show": False, "display_name": "Code"}, + "documents": {"display_name": "Documents", "is_list": True}, + "embedding": {"display_name": "Embedding"}, + "schema": {"display_name": "Schema", "file_types": [".yaml"]}, + "redis_server_url": { + "display_name": "Redis Server Connection String", + "advanced": False, + }, + "redis_index_name": {"display_name": "Redis Index", "advanced": False}, + } + + def build( + self, + input_value: str, + search_type: str, + embedding: Embeddings, + redis_server_url: str, + redis_index_name: str, + schema: Optional[str] = None, + ) -> List[Record]: + """ + Builds the Vector Store or BaseRetriever object. + + Args: + - embedding (Embeddings): The embeddings to use for the Vector Store. + - documents (Optional[Document]): The documents to use for the Vector Store. + - redis_index_name (str): The name of the Redis index. + - redis_server_url (str): The URL for the Redis server. + + Returns: + - VectorStore: The Vector Store object. + """ + vector_store = super().build( + embedding=embedding, + redis_server_url=redis_server_url, + redis_index_name=redis_index_name, + schema=schema, + ) + if not vector_store: + raise ValueError("Failed to load the Redis index.") + + return self.search_with_vector_store( + input_value=input_value, search_type=search_type, vector_store=vector_store + ) diff --git a/src/backend/langflow/components/vectorstores/SupabaseVectorStoreSearch.py b/src/backend/langflow/components/vectorstores/SupabaseVectorStoreSearch.py new file mode 100644 index 000000000..3a57f7951 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/SupabaseVectorStoreSearch.py @@ -0,0 +1,50 @@ +from typing import List + +from langchain_community.vectorstores.supabase import SupabaseVectorStore +from supabase.client import Client, create_client + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.field_typing import Embeddings +from langflow.schema import Record + + +class SupabaseSearchComponent(LCVectorStoreComponent): + display_name = "Supabase Search" + description = "Search a Supabase Vector Store for similar documents." + icon="Supabase" + + def build_config(self): + return { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, + "embedding": {"display_name": "Embedding"}, + "query_name": {"display_name": "Query Name"}, + "search_kwargs": {"display_name": "Search Kwargs", "advanced": True}, + "supabase_service_key": {"display_name": "Supabase Service Key"}, + "supabase_url": {"display_name": "Supabase URL"}, + "table_name": {"display_name": "Table Name", "advanced": True}, + } + + def build( + self, + input_value: str, + search_type: str, + embedding: Embeddings, + query_name: str = "", + supabase_service_key: str = "", + supabase_url: str = "", + table_name: str = "", + ) -> List[Record]: + supabase: Client = create_client( + supabase_url, supabase_key=supabase_service_key + ) + vector_store = SupabaseVectorStore( + client=supabase, + embedding=embedding, + table_name=table_name, + query_name=query_name, + ) + return self.search_with_vector_store(input_value, search_type, vector_store) diff --git a/src/backend/langflow/components/vectorstores/Vectara.py b/src/backend/langflow/components/vectorstores/Vectara.py index 31615fe7f..5ac105543 100644 --- a/src/backend/langflow/components/vectorstores/Vectara.py +++ b/src/backend/langflow/components/vectorstores/Vectara.py @@ -8,13 +8,17 @@ from langchain_community.vectorstores.vectara import Vectara from langchain_core.vectorstores import VectorStore from langflow import CustomComponent from langflow.field_typing import BaseRetriever, Document +from langchain_community.vectorstores.vectara import Vectara class VectaraComponent(CustomComponent): display_name: str = "Vectara" description: str = "Implementation of Vector Store using Vectara" - documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara" + documentation = ( + "https://python.langchain.com/docs/integrations/vectorstores/vectara" + ) beta = True + icon="Vectara" field_config = { "vectara_customer_id": { "display_name": "Vectara Customer ID", @@ -26,7 +30,10 @@ class VectaraComponent(CustomComponent): "display_name": "Vectara API Key", "password": True, }, - "documents": {"display_name": "Documents", "info": "If provided, will be upserted to corpus (optional)"}, + "documents": { + "display_name": "Documents", + "info": "If provided, will be upserted to corpus (optional)", + }, "files_url": { "display_name": "Files Url", "info": "Make vectara object using url of files (optional)", diff --git a/src/backend/langflow/components/vectorstores/VectaraSearch.py b/src/backend/langflow/components/vectorstores/VectaraSearch.py new file mode 100644 index 000000000..da2c083d1 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/VectaraSearch.py @@ -0,0 +1,66 @@ +from typing import List + +from langchain_community.vectorstores.vectara import Vectara + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.Vectara import VectaraComponent +from langflow.schema import Record + + +class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent): + display_name: str = "Vectara Search" + description: str = "Search a Vectara Vector Store for similar documents." + documentation = ( + "https://python.langchain.com/docs/integrations/vectorstores/vectara" + ) + beta = True + icon="Vectara" + + field_config = { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, + "vectara_customer_id": { + "display_name": "Vectara Customer ID", + }, + "vectara_corpus_id": { + "display_name": "Vectara Corpus ID", + }, + "vectara_api_key": { + "display_name": "Vectara API Key", + "password": True, + }, + "documents": { + "display_name": "Documents", + "info": "If provided, will be upserted to corpus (optional)", + }, + "files_url": { + "display_name": "Files Url", + "info": "Make vectara object using url of files (optional)", + }, + } + + def build( + self, + input_value: str, + search_type: str, + vectara_customer_id: str, + vectara_corpus_id: str, + vectara_api_key: str, + ) -> List[Record]: + source = "Langflow" + vector_store = Vectara( + vectara_customer_id=vectara_customer_id, + vectara_corpus_id=vectara_corpus_id, + vectara_api_key=vectara_api_key, + source=source, + ) + + if not vector_store: + raise ValueError("Failed to create Vectara Vector Store") + + return self.search_with_vector_store( + vector_store=vector_store, input_value=input_value, search_type=search_type + ) diff --git a/src/backend/langflow/components/vectorstores/Weaviate.py b/src/backend/langflow/components/vectorstores/Weaviate.py index 9b4967c36..59bbf4fef 100644 --- a/src/backend/langflow/components/vectorstores/Weaviate.py +++ b/src/backend/langflow/components/vectorstores/Weaviate.py @@ -8,10 +8,12 @@ from langchain_community.vectorstores import VectorStore, Weaviate from langflow import CustomComponent -class WeaviateVectorStore(CustomComponent): +class WeaviateVectorStoreComponent(CustomComponent): display_name: str = "Weaviate" description: str = "Implementation of Vector Store using Weaviate" - documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate" + documentation = ( + "https://python.langchain.com/docs/integrations/vectorstores/weaviate" + ) beta = True field_config = { "url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"}, @@ -24,7 +26,12 @@ class WeaviateVectorStore(CustomComponent): "display_name": "Index name", "required": False, }, - "text_key": {"display_name": "Text Key", "required": False, "advanced": True, "value": "text"}, + "text_key": { + "display_name": "Text Key", + "required": False, + "advanced": True, + "value": "text", + }, "documents": {"display_name": "Documents", "is_list": True}, "embedding": {"display_name": "Embedding"}, "attributes": { @@ -34,7 +41,11 @@ class WeaviateVectorStore(CustomComponent): "field_type": "str", "advanced": True, }, - "search_by_text": {"display_name": "Search By Text", "field_type": "bool", "advanced": True}, + "search_by_text": { + "display_name": "Search By Text", + "field_type": "bool", + "advanced": True, + }, "code": {"show": False}, } diff --git a/src/backend/langflow/components/vectorstores/WeaviateSearch.py b/src/backend/langflow/components/vectorstores/WeaviateSearch.py new file mode 100644 index 000000000..3eda5c583 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/WeaviateSearch.py @@ -0,0 +1,84 @@ +from typing import List, Optional + +from langchain.embeddings.base import Embeddings + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent +from langflow.schema import Record + + +class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent): + display_name: str = "Weaviate Search" + description: str = "Search a Weaviate Vector Store for similar documents." + documentation = ( + "https://python.langchain.com/docs/integrations/vectorstores/weaviate" + ) + beta = True + icon="Weaviate" + + field_config = { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": {"display_name": "Input"}, + "url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"}, + "api_key": { + "display_name": "API Key", + "password": True, + "required": False, + }, + "index_name": { + "display_name": "Index name", + "required": False, + }, + "text_key": { + "display_name": "Text Key", + "required": False, + "advanced": True, + "value": "text", + }, + "documents": {"display_name": "Documents", "is_list": True}, + "embedding": {"display_name": "Embedding"}, + "attributes": { + "display_name": "Attributes", + "required": False, + "is_list": True, + "field_type": "str", + "advanced": True, + }, + "search_by_text": { + "display_name": "Search By Text", + "field_type": "bool", + "advanced": True, + }, + "code": {"show": False}, + } + + def build( + self, + input_value: str, + search_type: str, + url: str, + search_by_text: bool = False, + api_key: Optional[str] = None, + index_name: Optional[str] = None, + text_key: str = "text", + embedding: Optional[Embeddings] = None, + attributes: Optional[list] = None, + ) -> List[Record]: + vector_store = super().build( + url=url, + api_key=api_key, + index_name=index_name, + text_key=text_key, + embedding=embedding, + attributes=attributes, + search_by_text=search_by_text, + ) + if not vector_store: + raise ValueError("Failed to load the Weaviate index.") + + return self.search_with_vector_store( + vector_store=vector_store, input_value=input_value, search_type=search_type + ) diff --git a/src/backend/langflow/components/vectorstores/base/__init__.py b/src/backend/langflow/components/vectorstores/base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/langflow/components/vectorstores/base/model.py b/src/backend/langflow/components/vectorstores/base/model.py new file mode 100644 index 000000000..1cc8b9d88 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/base/model.py @@ -0,0 +1,41 @@ +from typing import List + +from langchain_core.vectorstores import VectorStore + +from langflow import CustomComponent +from langflow.field_typing import Text +from langflow.schema import Record, docs_to_records + + +class LCVectorStoreComponent(CustomComponent): + + display_name: str = "LC Vector Store" + description: str = "Search a LC Vector Store for similar documents." + beta: bool = True + + def search_with_vector_store( + self, input_value: Text, search_type: str, vector_store: VectorStore + ) -> List[Record]: + """ + Search for records in the vector store based on the input value and search type. + + Args: + input_value (Text): The input value to search for. + search_type (str): The type of search to perform. + vector_store (VectorStore): The vector store to search in. + + Returns: + List[Record]: A list of records matching the search criteria. + + Raises: + ValueError: If invalid inputs are provided. + """ + + docs = [] + if input_value and isinstance(input_value, str): + docs = vector_store.search( + query=input_value, search_type=search_type.lower() + ) + else: + raise ValueError("Invalid inputs provided.") + return docs_to_records(docs) diff --git a/src/backend/langflow/components/vectorstores/pgvectorSearch.py b/src/backend/langflow/components/vectorstores/pgvectorSearch.py new file mode 100644 index 000000000..00e291e76 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/pgvectorSearch.py @@ -0,0 +1,73 @@ +from typing import List, Optional + +from langchain.embeddings.base import Embeddings + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.pgvector import PGVectorComponent +from langflow.schema import Record + + +class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent): + """ + A custom component for implementing a Vector Store using PostgreSQL. + """ + + display_name: str = "PGVector Search" + description: str = "Search a PGVector Store for similar documents." + documentation = ( + "https://python.langchain.com/docs/integrations/vectorstores/pgvector" + ) + + def build_config(self): + """ + Builds the configuration for the component. + + Returns: + - dict: A dictionary containing the configuration options for the component. + """ + return { + "code": {"show": False}, + "embedding": {"display_name": "Embedding"}, + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "pg_server_url": { + "display_name": "PostgreSQL Server Connection String", + "advanced": False, + }, + "collection_name": {"display_name": "Table", "advanced": False}, + "input_value": {"display_name": "Input"}, + } + + def build( + self, + input_value: str, + embedding: Embeddings, + pg_server_url: str, + collection_name: str, + search_type: Optional[str] = None, + ) -> List[Record]: + """ + Builds the Vector Store or BaseRetriever object. + + Args: + - input_value (str): The input value to search for. + - embedding (Embeddings): The embeddings to use for the Vector Store. + - collection_name (str): The name of the PG table. + - pg_server_url (str): The URL for the PG server. + + Returns: + - VectorStore: The Vector Store object. + """ + try: + vector_store = super().build( + embedding=embedding, + pg_server_url=pg_server_url, + collection_name=collection_name, + ) + except Exception as e: + raise RuntimeError(f"Failed to build PGVector: {e}") + return self.search_with_vector_store( + input_value=input_value, search_type=search_type, vector_store=vector_store + ) diff --git a/src/backend/langflow/config.yaml b/src/backend/langflow/config.yaml index df3b83434..102cb9016 100644 --- a/src/backend/langflow/config.yaml +++ b/src/backend/langflow/config.yaml @@ -218,24 +218,7 @@ retrievers: # https://github.com/supabase-community/supabase-py/issues/482 # ZepRetriever: # documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore" -vectorstores: - # Chroma: - # documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma" - Qdrant: - documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant" - FAISS: - documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss" - Pinecone: - documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/pinecone" - ElasticsearchStore: - documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/elasticsearch" - SupabaseVectorStore: - documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase" - MongoDBAtlasVectorSearch: - documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas" - # Requires docarray >=0.32.0 but langchain-serve requires jina 3.15.2 which doesn't support docarray >=0.32.0 - # DocArrayInMemorySearch: - # documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/docarray_in_memory" + wrappers: RequestsWrapper: documentation: "" diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py index 3f5e376a5..051fc6b3b 100644 --- a/src/backend/langflow/graph/graph/base.py +++ b/src/backend/langflow/graph/graph/base.py @@ -73,7 +73,7 @@ class Graph: if getattr(vertex, attribute): getattr(self, f"_{attribute}_vertices").append(vertex.id) - async def _run(self, inputs: Dict[str, str]) -> List["ResultData"]: + async def _run(self, inputs: Dict[str, str], stream: bool) -> List["ResultData"]: """Runs the graph with the given inputs.""" for vertex_id in self._is_input_vertices: vertex = self.get_vertex(vertex_id) @@ -91,10 +91,14 @@ class Graph: vertex = self.get_vertex(vertex_id) if vertex is None: raise ValueError(f"Vertex {vertex_id} not found") + if not stream and hasattr(vertex, "consume_async_generator"): + await vertex.consume_async_generator() outputs.append(vertex.result) return outputs - async def run(self, inputs: Dict[str, Union[str, list[str]]]) -> List["ResultData"]: + async def run( + self, inputs: Dict[str, Union[str, list[str]]], stream: bool + ) -> List["ResultData"]: """Runs the graph with the given inputs.""" # inputs is {"message": "Hello, world!"} @@ -106,7 +110,9 @@ class Graph: if not isinstance(inputs_values, list): inputs_values = [inputs_values] for input_value in inputs_values: - run_outputs = await self._run({INPUT_FIELD_NAME: input_value}) + run_outputs = await self._run( + {INPUT_FIELD_NAME: input_value}, stream=stream + ) logger.debug(f"Run outputs: {run_outputs}") outputs.extend(run_outputs) return outputs diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index dd308f9f1..fe2b29a02 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -44,6 +44,7 @@ class Vertex: ) -> None: # is_external means that the Vertex send or receives data from # an external source (e.g the chat) + self.will_stream = False self.updated_raw_params = False self.id: str = data["id"] self.is_input = any( @@ -391,6 +392,8 @@ class Vertex: ValueError: If any key in new_params is not found in self._raw_params. """ # First check if the input_value in _raw_params is not a vertex + if not new_params: + return if any(isinstance(self._raw_params.get(key), Vertex) for key in new_params): return self._raw_params.update(new_params) @@ -456,7 +459,7 @@ class Vertex: await self._build_node_and_update_params(key, value, user_id) elif isinstance(value, list) and self._is_list_of_nodes(value): await self._build_list_of_nodes_and_update_params(key, value, user_id) - elif key not in self.params: + elif key not in self.params or self.updated_raw_params: self.params[key] = value def _is_node(self, value): @@ -610,6 +613,7 @@ class Vertex: async def build( self, user_id=None, + inputs: Optional[Dict[str, Any]] = None, requester: Optional["Vertex"] = None, **kwargs, ) -> Any: @@ -622,6 +626,9 @@ class Vertex: return self.get_requester_result(requester) self._reset() + if self.is_input: + self.update_raw_params(inputs) + # Run steps for step in self.steps: if step not in self.steps_ran: diff --git a/src/backend/langflow/graph/vertex/types.py b/src/backend/langflow/graph/vertex/types.py index 100390d11..721a7ccc8 100644 --- a/src/backend/langflow/graph/vertex/types.py +++ b/src/backend/langflow/graph/vertex/types.py @@ -11,7 +11,7 @@ from langflow.graph.utils import UnbuiltObject, flatten_list from langflow.graph.vertex.base import StatefulVertex, StatelessVertex from langflow.interface.utils import extract_input_variables_from_prompt from langflow.schema import Record -from langflow.services.monitor.utils import log_message +from langflow.services.monitor.utils import log_vertex_build from langflow.utils.schemas import ChatOutputResponse @@ -394,6 +394,8 @@ class ChatVertex(StatelessVertex): sender_name=sender_name, stream_url=stream_url, ) + + self.will_stream = stream_url is not None if artifacts: self.artifacts = artifacts.model_dump() if isinstance(self._built_object, (AsyncIterator, Iterator)): @@ -434,19 +436,25 @@ class ChatVertex(StatelessVertex): self._built_result = complete_message # Update artifacts with the message # and remove the stream_url + self._finalize_build() logger.debug(f"Streamed message: {complete_message}") - await log_message( - sender=self.params.get("sender", ""), - sender_name=self.params.get("sender_name", ""), - message=complete_message, - session_id=self.params.get("session_id", ""), + await log_vertex_build( + flow_id=self.graph.flow_id, + vertex_id=self.id, + valid=True, + params=self._built_object_repr(), + data=self.result, artifacts=self.artifacts, ) self._validate_built_object() self._built = True + async def consume_async_generator(self): + async for _ in self.stream(): + pass + class RoutingVertex(StatelessVertex): def __init__(self, data: Dict, graph): diff --git a/src/backend/langflow/interface/custom/directory_reader/directory_reader.py b/src/backend/langflow/interface/custom/directory_reader/directory_reader.py index 57bacc9bc..278d014c3 100644 --- a/src/backend/langflow/interface/custom/directory_reader/directory_reader.py +++ b/src/backend/langflow/interface/custom/directory_reader/directory_reader.py @@ -1,6 +1,7 @@ import ast import os import zlib +from pathlib import Path from loguru import logger @@ -79,9 +80,13 @@ class DirectoryReader: except Exception as e: logger.error(f"Error while loading component: {e}") continue - items.append({"name": menu["name"], "path": menu["path"], "components": components}) + items.append( + {"name": menu["name"], "path": menu["path"], "components": components} + ) filtered = [menu for menu in items if menu["components"]] - logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}') + logger.debug( + f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}' + ) return {"menu": filtered} def validate_code(self, file_content): @@ -114,15 +119,24 @@ class DirectoryReader: Walk through the directory path and return a list of all .py files. """ if not (safe_path := self.get_safe_path()): - raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.") + raise CustomComponentPathValueError( + f"The path needs to start with '{self.base_path}'." + ) file_list = [] - for root, _, files in os.walk(safe_path): - file_list.extend( - os.path.join(root, filename) - for filename in files - if filename.endswith(".py") and not filename.startswith("__") - ) + safe_path_obj = Path(safe_path) + for file_path in safe_path_obj.rglob("*.py"): + # The other condtion is that it should be + # in the safe_path/[folder]/[file].py format + # any folders below [folder] will be ignored + # basically the parent folder of the file should be a + # folder in the safe_path + if ( + file_path.is_file() + and file_path.parent.parent == safe_path_obj + and not file_path.name.startswith("__") + ): + file_list.append(str(file_path)) return file_list def find_menu(self, response, menu_name): @@ -159,7 +173,9 @@ class DirectoryReader: for node in ast.walk(module): if isinstance(node, ast.FunctionDef): for arg in node.args.args: - if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name): + if self._is_type_hint_in_arg_annotation( + arg.annotation, type_hint_name + ): return True except SyntaxError: # Returns False if the code is not valid Python @@ -177,14 +193,16 @@ class DirectoryReader: and annotation.value.id == type_hint_name ) - def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool: + def is_type_hint_used_but_not_imported( + self, type_hint_name: str, code: str + ) -> bool: """ Check if a type hint is used but not imported in the given code. """ try: - return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported( + return self._is_type_hint_used_in_args( type_hint_name, code - ) + ) and not self._is_type_hint_imported(type_hint_name, code) except SyntaxError: # Returns True if there's something wrong with the code # TODO : Find a better way to handle this @@ -205,9 +223,9 @@ class DirectoryReader: return False, "Syntax error" elif not self.validate_build(file_content): return False, "Missing build function" - elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported( + elif self._is_type_hint_used_in_args( "Optional", file_content - ): + ) and not self._is_type_hint_imported("Optional", file_content): return ( False, "Type hint 'Optional' is used but not imported in the code.", @@ -223,7 +241,9 @@ class DirectoryReader: from the .py files in the directory. """ response = {"menu": []} - logger.debug("-------------------- Building component menu list --------------------") + logger.debug( + "-------------------- Building component menu list --------------------" + ) for file_path in file_paths: menu_name = os.path.basename(os.path.dirname(file_path)) @@ -243,7 +263,9 @@ class DirectoryReader: # first check if it's already CamelCase if "_" in component_name: - component_name_camelcase = " ".join(word.title() for word in component_name.split("_")) + component_name_camelcase = " ".join( + word.title() for word in component_name.split("_") + ) else: component_name_camelcase = component_name @@ -251,7 +273,9 @@ class DirectoryReader: try: output_types = self.get_output_types_from_code(result_content) except Exception as exc: - logger.exception(f"Error while getting output types from code: {str(exc)}") + logger.exception( + f"Error while getting output types from code: {str(exc)}" + ) output_types = [component_name_camelcase] else: output_types = [component_name_camelcase] @@ -267,7 +291,9 @@ class DirectoryReader: if menu_result not in response["menu"]: response["menu"].append(menu_result) - logger.debug("-------------------- Component menu list built --------------------") + logger.debug( + "-------------------- Component menu list built --------------------" + ) return response @staticmethod diff --git a/src/backend/langflow/interface/custom/directory_reader/utils.py b/src/backend/langflow/interface/custom/directory_reader/utils.py index f7378b8d0..34defe5c3 100644 --- a/src/backend/langflow/interface/custom/directory_reader/utils.py +++ b/src/backend/langflow/interface/custom/directory_reader/utils.py @@ -1,11 +1,18 @@ -from langflow.interface.custom.directory_reader import DirectoryReader -from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from loguru import logger +from langflow.interface.custom.directory_reader import DirectoryReader +from langflow.template.frontend_node.custom_components import ( + CustomComponentFrontendNode, +) + def merge_nested_dicts_with_renaming(dict1, dict2): for key, value in dict2.items(): - if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict): + if ( + key in dict1 + and isinstance(value, dict) + and isinstance(dict1.get(key), dict) + ): for sub_key, sub_value in value.items(): # if sub_key in dict1[key]: # new_key = get_new_key(dict1[key], sub_key) @@ -62,7 +69,9 @@ def build_custom_component_list_from_path(path: str): file_list = load_files_from_path(path) reader = DirectoryReader(path, False) - valid_components, invalid_components = build_and_validate_all_files(reader, file_list) + valid_components, invalid_components = build_and_validate_all_files( + reader, file_list + ) valid_menu = build_valid_menu(valid_components) invalid_menu = build_invalid_menu(invalid_components) @@ -109,7 +118,9 @@ def build_invalid_menu_items(menu_item): menu_items[component_name] = component_template logger.debug(f"Added {component_name} to invalid menu.") except Exception as exc: - logger.exception(f"Error while creating custom component [{component_name}]: {str(exc)}") + logger.exception( + f"Error while creating custom component [{component_name}]: {str(exc)}" + ) return menu_items @@ -136,12 +147,14 @@ def determine_component_name(component): def build_menu_items(menu_item): """Build menu items for a given menu.""" menu_items = {} + logger.debug(f"Building menu items for {menu_item['name']}") + logger.debug(f"Loading {len(menu_item['components'])} components") for component_name, component_template, component in menu_item["components"]: try: menu_items[component_name] = component_template - logger.debug(f"Added {component_name} to valid menu.") except Exception as exc: logger.error(f"Error loading Component: {component['output_types']}") - logger.exception(f"Error while building custom component {component['output_types']}: {exc}") - return menu_items + logger.exception( + f"Error while building custom component {component['output_types']}: {exc}" + ) return menu_items diff --git a/src/backend/langflow/processing/process.py b/src/backend/langflow/processing/process.py index aa419b962..d7cf09a6f 100644 --- a/src/backend/langflow/processing/process.py +++ b/src/backend/langflow/processing/process.py @@ -271,18 +271,26 @@ async def run_graph( graph: Union["Graph", dict], flow_id: str, session_id: str, + stream: bool, inputs: Optional[Union[dict, List[dict]]] = None, artifacts: Optional[Dict[str, Any]] = None, session_service: Optional[SessionService] = None, ): """Run the graph and generate the result""" if isinstance(graph, dict): + graph_data = graph graph = Graph.from_payload(graph, flow_id=flow_id) + else: + graph_data = graph._graph_data + if not session_id: + session_id = session_service.generate_key( + session_id=flow_id, data_graph=graph_data + ) - outputs = await graph.run(inputs) + outputs = await graph.run(inputs, stream=stream) if session_id and session_service: session_service.update_session(session_id, (graph, artifacts)) - return outputs + return outputs, session_id def validate_input( diff --git a/src/backend/langflow/services/database/service.py b/src/backend/langflow/services/database/service.py index 693da8143..9765d858f 100644 --- a/src/backend/langflow/services/database/service.py +++ b/src/backend/langflow/services/database/service.py @@ -5,16 +5,17 @@ from typing import TYPE_CHECKING import sqlalchemy as sa from alembic import command, util from alembic.config import Config +from loguru import logger +from sqlalchemy import inspect +from sqlalchemy.exc import OperationalError +from sqlmodel import Session, SQLModel, create_engine, select, text + from langflow.services.base import Service from langflow.services.database import models # noqa from langflow.services.database.models.user.crud import get_user_by_username from langflow.services.database.utils import Result, TableResults from langflow.services.deps import get_settings_service from langflow.services.utils import teardown_superuser -from loguru import logger -from sqlalchemy import inspect -from sqlalchemy.exc import OperationalError -from sqlmodel import Session, SQLModel, create_engine, select, text if TYPE_CHECKING: from sqlalchemy.engine import Engine @@ -39,7 +40,7 @@ class DatabaseService(Service): connect_args = {"check_same_thread": False} else: connect_args = {} - return create_engine(self.database_url, connect_args=connect_args, max_overflow=-1) + return create_engine(self.database_url, connect_args=connect_args) def __enter__(self): self._session = Session(self.engine) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 8a83aac62..f0552bdc7 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -8,7 +8,11 @@ import Checkmark from "../../components/ui/checkmark"; import Loading from "../../components/ui/loading"; import { Textarea } from "../../components/ui/textarea"; import Xmark from "../../components/ui/xmark"; -import { priorityFields, statusBuild, statusBuilding } from "../../constants/constants"; +import { + priorityFields, + statusBuild, + statusBuilding, +} from "../../constants/constants"; import { BuildStatus } from "../../constants/enums"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import { useDarkStore } from "../../stores/darkStore"; @@ -211,9 +215,7 @@ export default function GenericNode({ return "inactive-status"; } if (buildStatus === BuildStatus.BUILT && isInvalid) { - return isDark - ? "built-invalid-status-dark" - : "built-invalid-status"; + return isDark ? "built-invalid-status-dark" : "built-invalid-status"; } else if (buildStatus === BuildStatus.BUILDING) { return "building-status"; } else { @@ -296,7 +298,7 @@ export default function GenericNode({
{iconNodeRender()} @@ -332,21 +334,20 @@ export default function GenericNode({ ) : (
- -
{ - if (nameEditable) { - setInputName(true); - } - takeSnapshot(); - event.stopPropagation(); - event.preventDefault(); - }} - data-testid={"title-" + data.node?.display_name} - className="generic-node-tooltip-div text-primary" - > - {data.node?.display_name} -
+
{ + if (nameEditable) { + setInputName(true); + } + takeSnapshot(); + event.stopPropagation(); + event.preventDefault(); + }} + data-testid={"title-" + data.node?.display_name} + className="generic-node-tooltip-div text-primary" + > + {data.node?.display_name} +
{nameEditable && (
@@ -478,11 +479,11 @@ export default function GenericNode({ ) : (
{typeof validationStatus.params === "string" - ? (`${durationString}\n${validationStatus.params}` + ? `${durationString}\n${validationStatus.params}` .split("\n") .map((line, index) => (
{line}
- ))) + )) : durationString}
) diff --git a/src/frontend/src/alerts/alertDropDown/index.tsx b/src/frontend/src/alerts/alertDropDown/index.tsx index cf3fa13dc..f1eab4c60 100644 --- a/src/frontend/src/alerts/alertDropDown/index.tsx +++ b/src/frontend/src/alerts/alertDropDown/index.tsx @@ -1,3 +1,4 @@ +import { Cross2Icon } from "@radix-ui/react-icons"; import { useState } from "react"; import IconComponent from "../../components/genericIconComponent"; import { @@ -46,15 +47,15 @@ export default function AlertDropdown({ setTimeout(clearNotificationList, 100); }} > - +
diff --git a/src/frontend/src/components/IOInputField/index.tsx b/src/frontend/src/components/IOInputField/index.tsx index e7aac5928..20c6dceb3 100644 --- a/src/frontend/src/components/IOInputField/index.tsx +++ b/src/frontend/src/components/IOInputField/index.tsx @@ -19,12 +19,12 @@ export default function IOInputField({