From 116518202c306b7f82138fda4e5728f82add8070 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 21:25:27 -0300 Subject: [PATCH 01/50] Refactor vertex streaming logic in build_vertex_stream function --- src/backend/langflow/api/v1/chat.py | 36 ++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index e1b52aeb5..beb9b19b2 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -185,15 +185,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) @@ -243,22 +244,31 @@ async def build_vertex_stream( 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", From a9d183bab0e17c9d433634b46cd06e66f775e9d4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 21:25:37 -0300 Subject: [PATCH 02/50] Refactor typing imports and add 'will_stream' attribute to Vertex class --- src/backend/langflow/graph/vertex/base.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index dd308f9f1..b917889a4 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -2,16 +2,13 @@ import ast import inspect import types from enum import Enum -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional +from typing import (TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, + Optional) from loguru import logger -from langflow.graph.schema import ( - INPUT_COMPONENTS, - OUTPUT_COMPONENTS, - InterfaceComponentTypes, - ResultData, -) +from langflow.graph.schema import (INPUT_COMPONENTS, OUTPUT_COMPONENTS, + InterfaceComponentTypes, ResultData) from langflow.graph.utils import UnbuiltObject, UnbuiltResult from langflow.graph.vertex.utils import generate_result from langflow.interface.initialize import loading @@ -44,6 +41,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( From b55be2aba02b40faac57d3a1ee7768ce1d7b3f2f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 21:26:18 -0300 Subject: [PATCH 03/50] Refactor ChatVertex to include streaming capability --- src/backend/langflow/graph/vertex/types.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/backend/langflow/graph/vertex/types.py b/src/backend/langflow/graph/vertex/types.py index 100390d11..0b2be3ab0 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,13 +436,15 @@ 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, ) From a2eaed1af3412fd4c2c3ea2f51c1847528900fa2 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 27 Feb 2024 21:46:06 -0300 Subject: [PATCH 04/50] Update sender value in updateFlowPool function --- src/frontend/src/components/newChatView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/newChatView/index.tsx b/src/frontend/src/components/newChatView/index.tsx index b1f888f1c..7a9c869c5 100644 --- a/src/frontend/src/components/newChatView/index.tsx +++ b/src/frontend/src/components/newChatView/index.tsx @@ -124,7 +124,7 @@ export default function NewChatView({ if (message === "") return; chat.message = message; // chat is one of the chatHistory - updateFlowPool(chat.componentId,{message,sender_name:chat.sender_name??"Bot",sender:"Machine"}) + updateFlowPool(chat.componentId,{message,sender_name:chat.sender_name??"Bot",sender:chat.isSend?"User":"Machine"}) // setChatHistory((oldChatHistory) => { // const index = oldChatHistory.findIndex((ch) => ch.id === chat.id); // if (index === -1) return oldChatHistory; From cf35b42b8e3477bc45998422b49f8522b03964d8 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 21:50:27 -0300 Subject: [PATCH 05/50] Refactor newChatView component --- .../src/components/newChatView/index.tsx | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/components/newChatView/index.tsx b/src/frontend/src/components/newChatView/index.tsx index 7a9c869c5..c4be0bcc7 100644 --- a/src/frontend/src/components/newChatView/index.tsx +++ b/src/frontend/src/components/newChatView/index.tsx @@ -1,6 +1,10 @@ -import _ from "lodash"; import { useEffect, useRef, useState } from "react"; import IconComponent from "../../components/genericIconComponent"; +import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants"; +import { + chatFirstInitialText, + chatSecondInitialText, +} from "../../constants/constants"; import { deleteFlowPool } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import useFlowStore from "../../stores/flowStore"; @@ -14,8 +18,6 @@ import { import { classNames } from "../../utils/utils"; import ChatInput from "./chatInput"; import ChatMessage from "./chatMessage"; -import { INFO_MISSING_ALERT, NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants"; -import { chatFirstInitialText, chatSecondInitialText } from "../../constants/constants"; export default function NewChatView({ sendMessage, @@ -34,7 +36,7 @@ export default function NewChatView({ const inputIds = inputs.map((obj) => obj.id); const outputIds = outputs.map((obj) => obj.id); const outputTypes = outputs.map((obj) => obj.type); - const updateFlowPool = useFlowStore((state)=>state.updateFlowPool) + const updateFlowPool = useFlowStore((state) => state.updateFlowPool); useEffect(() => { if (!outputTypes.includes("ChatOutput")) { @@ -73,7 +75,7 @@ export default function NewChatView({ isSend: !is_ai, message: message, sender_name, - componentId: output.id, + componentId: output.id, stream_url: stream_url, }; } catch (e) { @@ -120,22 +122,26 @@ export default function NewChatView({ chat: ChatMessageType, message: string, stream_url?: string - ) { - if (message === "") return; - chat.message = message; + ) { + if (message === "") return; + chat.message = message; // chat is one of the chatHistory - updateFlowPool(chat.componentId,{message,sender_name:chat.sender_name??"Bot",sender:chat.isSend?"User":"Machine"}) + updateFlowPool(chat.componentId, { + message, + sender_name: chat.sender_name ?? "Bot", + sender: chat.isSend ? "User" : "Machine", + }); // setChatHistory((oldChatHistory) => { - // const index = oldChatHistory.findIndex((ch) => ch.id === chat.id); - // if (index === -1) return oldChatHistory; - // let newChatHistory = _.cloneDeep(oldChatHistory); - // newChatHistory = [ - // ...newChatHistory.slice(0, index), - // chat, - // ...newChatHistory.slice(index + 1), - // ]; - // console.log("newChatHistory:", newChatHistory); - // return newChatHistory; + // const index = oldChatHistory.findIndex((ch) => ch.id === chat.id); + // if (index === -1) return oldChatHistory; + // let newChatHistory = _.cloneDeep(oldChatHistory); + // newChatHistory = [ + // ...newChatHistory.slice(0, index), + // chat, + // ...newChatHistory.slice(index + 1), + // ]; + // console.log("newChatHistory:", newChatHistory); + // return newChatHistory; // }); } @@ -160,7 +166,7 @@ export default function NewChatView({ {chatHistory?.length > 0 ? ( chatHistory.map((chat, index) => ( Date: Tue, 27 Feb 2024 21:50:34 -0300 Subject: [PATCH 06/50] Refactor imports and remove console.log statements --- .../newChatView/chatMessage/index.tsx | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/src/frontend/src/components/newChatView/chatMessage/index.tsx b/src/frontend/src/components/newChatView/chatMessage/index.tsx index 4f6d8e534..7c606679f 100644 --- a/src/frontend/src/components/newChatView/chatMessage/index.tsx +++ b/src/frontend/src/components/newChatView/chatMessage/index.tsx @@ -1,5 +1,5 @@ import Convert from "ansi-to-html"; -import { useEffect, useMemo, useState, useRef } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import Markdown from "react-markdown"; import rehypeMathjax from "rehype-mathjax"; import remarkGfm from "remark-gfm"; @@ -9,17 +9,17 @@ import Robot from "../../../assets/robot.png"; import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper"; import CodeTabsComponent from "../../../components/codeTabsComponent"; import IconComponent from "../../../components/genericIconComponent"; +import useFlowStore from "../../../stores/flowStore"; import { chatMessagePropsType } from "../../../types/components"; import { classNames } from "../../../utils/utils"; import FileCard from "../fileComponent"; -import useFlowStore from "../../../stores/flowStore"; export default function ChatMessage({ chat, lockChat, lastMessage, updateChat, - setLockChat + setLockChat, }: chatMessagePropsType): JSX.Element { const convert = new Convert({ newline: true }); const [hidden, setHidden] = useState(true); @@ -40,8 +40,6 @@ export default function ChatMessage({ chatMessageRef.current = chatMessage; }, [chatMessage]); - - // The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat // probably the message is empty when we have a stream_url // what we need is to update the chat_message with the SSE data @@ -70,9 +68,7 @@ export default function ChatMessage({ }); }; - useEffect(() => { - console.log("chatMessage", chatMessage); if (streamUrl && !isStreaming) { setLockChat(true); streamChunks(streamUrl) @@ -92,8 +88,8 @@ export default function ChatMessage({ useEffect(() => { return () => { eventSource.current?.close(); - } - }, []) + }; + }, []); useEffect(() => { const element = document.getElementById("last-chat-message"); @@ -222,7 +218,7 @@ dark:prose-invert" }, ]} activeTab={"0"} - setActiveTab={() => { }} + setActiveTab={() => {}} /> ) : ( @@ -279,33 +275,33 @@ dark:prose-invert" {promptOpen ? template?.split("\n")?.map((line, index) => { - const regex = /{([^}]+)}/g; - let match; - let parts: Array = []; - let lastIndex = 0; - while ((match = regex.exec(line)) !== null) { - // Push text up to the match - if (match.index !== lastIndex) { - parts.push(line.substring(lastIndex, match.index)); - } - // Push div with matched text - if (chat.message[match[1]]) { - parts.push( - - {chat.message[match[1]]} - - ); - } + const regex = /{([^}]+)}/g; + let match; + let parts: Array = []; + let lastIndex = 0; + while ((match = regex.exec(line)) !== null) { + // Push text up to the match + if (match.index !== lastIndex) { + parts.push(line.substring(lastIndex, match.index)); + } + // Push div with matched text + if (chat.message[match[1]]) { + parts.push( + + {chat.message[match[1]]} + + ); + } - // Update last index - lastIndex = regex.lastIndex; - } - // Push text after the last match - if (lastIndex !== line.length) { - parts.push(line.substring(lastIndex)); - } - return

{parts}

; - }) + // Update last index + lastIndex = regex.lastIndex; + } + // Push text after the last match + if (lastIndex !== line.length) { + parts.push(line.substring(lastIndex)); + } + return

{parts}

; + }) : chatMessage}
From 0a77490f8c5c4064bca96d11a7e7862551649ba1 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 22:03:40 -0300 Subject: [PATCH 07/50] Update input field names in model components --- .../langflow/components/chains/RetrievalQA.py | 5 ++++- src/backend/langflow/components/io/TextInput.py | 12 ++++++------ .../langflow/components/models/AmazonBedrockModel.py | 2 +- .../langflow/components/models/AnthropicModel.py | 2 +- .../langflow/components/models/AzureOpenAIModel.py | 2 +- .../components/models/BaiduQianfanChatModel.py | 2 +- .../langflow/components/models/CTransformersModel.py | 2 +- .../langflow/components/models/CohereModel.py | 2 +- .../components/models/GoogleGenerativeAIModel.py | 7 ++----- .../langflow/components/models/HuggingFaceModel.py | 2 +- .../langflow/components/models/LlamaCppModel.py | 2 +- .../langflow/components/models/OllamaModel.py | 2 +- .../langflow/components/models/OpenAIModel.py | 2 +- .../langflow/components/models/VertexAiModel.py | 2 +- .../components/utilities/RunnableExecutor.py | 2 +- .../langflow/components/vectorstores/ChromaSearch.py | 2 +- 16 files changed, 25 insertions(+), 25 deletions(-) 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/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/components/models/AmazonBedrockModel.py b/src/backend/langflow/components/models/AmazonBedrockModel.py index 478bf8e9a..70daa2b4c 100644 --- a/src/backend/langflow/components/models/AmazonBedrockModel.py +++ b/src/backend/langflow/components/models/AmazonBedrockModel.py @@ -34,7 +34,7 @@ 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"}, } def build( diff --git a/src/backend/langflow/components/models/AnthropicModel.py b/src/backend/langflow/components/models/AnthropicModel.py index cb8e55194..fe3d63d34 100644 --- a/src/backend/langflow/components/models/AnthropicModel.py +++ b/src/backend/langflow/components/models/AnthropicModel.py @@ -49,7 +49,7 @@ 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"}, } def build( diff --git a/src/backend/langflow/components/models/AzureOpenAIModel.py b/src/backend/langflow/components/models/AzureOpenAIModel.py index df8413870..4abd7fcac 100644 --- a/src/backend/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/langflow/components/models/AzureOpenAIModel.py @@ -73,7 +73,7 @@ class AzureChatOpenAIComponent(CustomComponent): "info": "Maximum number of tokens to generate.", }, "code": {"show": False}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, } def build( diff --git a/src/backend/langflow/components/models/BaiduQianfanChatModel.py b/src/backend/langflow/components/models/BaiduQianfanChatModel.py index 0075316a2..af76262f8 100644 --- a/src/backend/langflow/components/models/BaiduQianfanChatModel.py +++ b/src/backend/langflow/components/models/BaiduQianfanChatModel.py @@ -68,7 +68,7 @@ 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"}, } def build( diff --git a/src/backend/langflow/components/models/CTransformersModel.py b/src/backend/langflow/components/models/CTransformersModel.py index fb292d9cf..8a556a954 100644 --- a/src/backend/langflow/components/models/CTransformersModel.py +++ b/src/backend/langflow/components/models/CTransformersModel.py @@ -28,7 +28,7 @@ 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"}, } def build( diff --git a/src/backend/langflow/components/models/CohereModel.py b/src/backend/langflow/components/models/CohereModel.py index c2a004c38..2a6bf613b 100644 --- a/src/backend/langflow/components/models/CohereModel.py +++ b/src/backend/langflow/components/models/CohereModel.py @@ -28,7 +28,7 @@ class CohereComponent(CustomComponent): "type": "float", "show": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, } def build( diff --git a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py index 3b0c758e8..192fef8da 100644 --- a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py +++ b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py @@ -1,10 +1,7 @@ -from typing import Optional -from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore -from pydantic.v1.types import SecretStr from langflow import CustomComponent -from langflow.field_typing import RangeSpec, Text +from langflow.field_typing import RangeSpec class GoogleGenerativeAIComponent(CustomComponent): @@ -50,7 +47,7 @@ class GoogleGenerativeAIComponent(CustomComponent): "code": { "advanced": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {e": {"display_name": "Input"}, } def build( diff --git a/src/backend/langflow/components/models/HuggingFaceModel.py b/src/backend/langflow/components/models/HuggingFaceModel.py index 99fcc6ab0..3fe97aca1 100644 --- a/src/backend/langflow/components/models/HuggingFaceModel.py +++ b/src/backend/langflow/components/models/HuggingFaceModel.py @@ -24,7 +24,7 @@ class HuggingFaceEndpointsComponent(CustomComponent): "field_type": "code", }, "code": {"show": False}, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, } def build( diff --git a/src/backend/langflow/components/models/LlamaCppModel.py b/src/backend/langflow/components/models/LlamaCppModel.py index 5941f213b..d3080b8b5 100644 --- a/src/backend/langflow/components/models/LlamaCppModel.py +++ b/src/backend/langflow/components/models/LlamaCppModel.py @@ -56,7 +56,7 @@ 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"}, } def build( diff --git a/src/backend/langflow/components/models/OllamaModel.py b/src/backend/langflow/components/models/OllamaModel.py index 7ae896532..72e3617df 100644 --- a/src/backend/langflow/components/models/OllamaModel.py +++ b/src/backend/langflow/components/models/OllamaModel.py @@ -164,7 +164,7 @@ class ChatOllamaComponent(CustomComponent): "info": "Template to use for generating text.", "advanced": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, } def build( diff --git a/src/backend/langflow/components/models/OpenAIModel.py b/src/backend/langflow/components/models/OpenAIModel.py index ee6809e0b..6a8d5c19e 100644 --- a/src/backend/langflow/components/models/OpenAIModel.py +++ b/src/backend/langflow/components/models/OpenAIModel.py @@ -12,7 +12,7 @@ class OpenAIModelComponent(CustomComponent): def build_config(self): return { - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, "max_tokens": { "display_name": "Max Tokens", "advanced": False, diff --git a/src/backend/langflow/components/models/VertexAiModel.py b/src/backend/langflow/components/models/VertexAiModel.py index c05dc5e94..a8fd0eb1f 100644 --- a/src/backend/langflow/components/models/VertexAiModel.py +++ b/src/backend/langflow/components/models/VertexAiModel.py @@ -57,7 +57,7 @@ class ChatVertexAIComponent(CustomComponent): "value": False, "advanced": True, }, - "inputs": {"display_name": "Input"}, + "input_value": {"display_name": "Input"}, } def build( 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..6cb3e89df 100644 --- a/src/backend/langflow/components/vectorstores/ChromaSearch.py +++ b/src/backend/langflow/components/vectorstores/ChromaSearch.py @@ -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"], From 8dd3cccfbe1282a0b520bbdc1f7ab3ad8527c305 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 22:37:38 -0300 Subject: [PATCH 08/50] Refactor run_graph function to generate session_id if not provided --- src/backend/langflow/processing/process.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/processing/process.py b/src/backend/langflow/processing/process.py index aa419b962..0ab6fcadd 100644 --- a/src/backend/langflow/processing/process.py +++ b/src/backend/langflow/processing/process.py @@ -277,12 +277,19 @@ async def run_graph( ): """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) if session_id and session_service: session_service.update_session(session_id, (graph, artifacts)) - return outputs + return outputs, session_id def validate_input( From fb797a7eea390612455f10e55c70c50dd275969d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 22:37:48 -0300 Subject: [PATCH 09/50] Fix run_graph function call in endpoints.py --- src/backend/langflow/api/v1/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index fce4f9649..8da1f0e53 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -239,7 +239,7 @@ 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, @@ -263,7 +263,7 @@ 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, From 13317d61e2f961648899639315131bfc1443b4e9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 22:37:55 -0300 Subject: [PATCH 10/50] Refactor build_vertex_stream function to handle session_id parameter --- src/backend/langflow/api/v1/chat.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index beb9b19b2..91b4bcd65 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -32,8 +32,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 @@ -227,19 +228,27 @@ 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"): From 6c2a35afb1940a36aad8ccb8fef00fcbc094fb16 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 22:44:50 -0300 Subject: [PATCH 11/50] Refactor model components to support streaming --- .../components/models/AmazonBedrockModel.py | 14 +++++++++++--- .../components/models/AnthropicModel.py | 14 +++++++++++--- .../components/models/AzureOpenAIModel.py | 14 +++++++++++--- .../components/models/BaiduQianfanChatModel.py | 14 +++++++++++--- .../components/models/CTransformersModel.py | 14 +++++++++++--- .../langflow/components/models/CohereModel.py | 12 +++++++----- .../models/GoogleGenerativeAIModel.py | 11 +++++++---- .../langflow/components/models/LlamaCppModel.py | 16 +++++++++++----- .../langflow/components/models/OllamaModel.py | 14 +++++++++++--- .../langflow/components/models/OpenAIModel.py | 17 ++++++++++++----- .../langflow/components/models/VertexAiModel.py | 14 +++++++++++--- 11 files changed, 114 insertions(+), 40 deletions(-) diff --git a/src/backend/langflow/components/models/AmazonBedrockModel.py b/src/backend/langflow/components/models/AmazonBedrockModel.py index 70daa2b4c..761daae65 100644 --- a/src/backend/langflow/components/models/AmazonBedrockModel.py +++ b/src/backend/langflow/components/models/AmazonBedrockModel.py @@ -35,6 +35,10 @@ class AmazonBedrockComponent(CustomComponent): "cache": {"display_name": "Cache"}, "code": {"advanced": True}, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -47,6 +51,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 +65,10 @@ 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 + 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/models/AnthropicModel.py b/src/backend/langflow/components/models/AnthropicModel.py index fe3d63d34..230a5ab2a 100644 --- a/src/backend/langflow/components/models/AnthropicModel.py +++ b/src/backend/langflow/components/models/AnthropicModel.py @@ -50,6 +50,10 @@ class AnthropicLLM(CustomComponent): }, "code": {"show": False}, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -60,6 +64,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 +82,10 @@ 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 + 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/models/AzureOpenAIModel.py b/src/backend/langflow/components/models/AzureOpenAIModel.py index 4abd7fcac..8931bd6e4 100644 --- a/src/backend/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/langflow/components/models/AzureOpenAIModel.py @@ -74,6 +74,10 @@ class AzureChatOpenAIComponent(CustomComponent): }, "code": {"show": False}, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -86,6 +90,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 +104,10 @@ 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 + 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/models/BaiduQianfanChatModel.py b/src/backend/langflow/components/models/BaiduQianfanChatModel.py index af76262f8..121dd9be6 100644 --- a/src/backend/langflow/components/models/BaiduQianfanChatModel.py +++ b/src/backend/langflow/components/models/BaiduQianfanChatModel.py @@ -69,6 +69,10 @@ class QianfanChatEndpointComponent(CustomComponent): }, "code": {"show": False}, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -81,6 +85,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 +99,10 @@ 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 + 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/models/CTransformersModel.py b/src/backend/langflow/components/models/CTransformersModel.py index 8a556a954..9784f86b1 100644 --- a/src/backend/langflow/components/models/CTransformersModel.py +++ b/src/backend/langflow/components/models/CTransformersModel.py @@ -29,6 +29,10 @@ class CTransformersComponent(CustomComponent): "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}', }, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -38,11 +42,15 @@ 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 + 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/models/CohereModel.py b/src/backend/langflow/components/models/CohereModel.py index 2a6bf613b..6af3971da 100644 --- a/src/backend/langflow/components/models/CohereModel.py +++ b/src/backend/langflow/components/models/CohereModel.py @@ -43,8 +43,10 @@ class CohereComponent(CustomComponent): 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 + 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 return result diff --git a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py index 192fef8da..40f1d385f 100644 --- a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py +++ b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py @@ -70,7 +70,10 @@ 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 + 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 \ No newline at end of file diff --git a/src/backend/langflow/components/models/LlamaCppModel.py b/src/backend/langflow/components/models/LlamaCppModel.py index d3080b8b5..00c0ee0f4 100644 --- a/src/backend/langflow/components/models/LlamaCppModel.py +++ b/src/backend/langflow/components/models/LlamaCppModel.py @@ -57,6 +57,10 @@ class LlamaCppComponent(CustomComponent): "verbose": {"display_name": "Verbose", "advanced": True}, "vocab_only": {"display_name": "Vocab Only", "advanced": True}, "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,10 @@ 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 + 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/models/OllamaModel.py b/src/backend/langflow/components/models/OllamaModel.py index 72e3617df..5f0a86289 100644 --- a/src/backend/langflow/components/models/OllamaModel.py +++ b/src/backend/langflow/components/models/OllamaModel.py @@ -165,6 +165,10 @@ class ChatOllamaComponent(CustomComponent): "advanced": True, }, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -197,6 +201,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 +255,10 @@ 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 + 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/models/OpenAIModel.py b/src/backend/langflow/components/models/OpenAIModel.py index 6a8d5c19e..34a0252be 100644 --- a/src/backend/langflow/components/models/OpenAIModel.py +++ b/src/backend/langflow/components/models/OpenAIModel.py @@ -57,6 +57,10 @@ class OpenAIModelComponent(CustomComponent): "required": False, "value": 0.7, }, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -68,10 +72,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, @@ -79,8 +84,10 @@ class OpenAIModelComponent(CustomComponent): api_key=openai_api_key, temperature=temperature, ) - - message = model.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result + 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/models/VertexAiModel.py b/src/backend/langflow/components/models/VertexAiModel.py index a8fd0eb1f..dcaa95860 100644 --- a/src/backend/langflow/components/models/VertexAiModel.py +++ b/src/backend/langflow/components/models/VertexAiModel.py @@ -58,6 +58,10 @@ class ChatVertexAIComponent(CustomComponent): "advanced": True, }, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -73,6 +77,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 +97,10 @@ 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 + 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 From 639c54e3eefa1aae137ee3e89eb9466dccd557ec Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 23:04:21 -0300 Subject: [PATCH 12/50] Add stream parameter to run_flow_with_caching and Graph.run methods --- src/backend/langflow/api/v1/endpoints.py | 3 +++ src/backend/langflow/graph/graph/base.py | 12 +++++++++--- src/backend/langflow/graph/vertex/types.py | 4 ++++ src/backend/langflow/processing/process.py | 3 ++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 8da1f0e53..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), @@ -246,6 +247,7 @@ async def run_flow_with_caching( inputs=inputs, artifacts=artifacts, session_service=session_service, + stream=stream, ) else: @@ -270,6 +272,7 @@ async def run_flow_with_caching( inputs=inputs, artifacts={}, session_service=session_service, + stream=stream, ) return RunResponse(outputs=task_result, session_id=session_id) 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/types.py b/src/backend/langflow/graph/vertex/types.py index 0b2be3ab0..721a7ccc8 100644 --- a/src/backend/langflow/graph/vertex/types.py +++ b/src/backend/langflow/graph/vertex/types.py @@ -451,6 +451,10 @@ class ChatVertex(StatelessVertex): 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/processing/process.py b/src/backend/langflow/processing/process.py index 0ab6fcadd..d7cf09a6f 100644 --- a/src/backend/langflow/processing/process.py +++ b/src/backend/langflow/processing/process.py @@ -271,6 +271,7 @@ 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, @@ -286,7 +287,7 @@ async def run_graph( 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, session_id From 2b556d5fb3ff67f8bf38a86f1ca2658567a759cd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 23:48:04 -0300 Subject: [PATCH 13/50] Add base chat component for chat input and output --- src/backend/langflow/components/io/ChatInput.py | 2 +- src/backend/langflow/components/io/ChatOutput.py | 2 +- src/backend/langflow/{io => components/io/base}/__init__.py | 0 .../langflow/{io/schema.py => components/io/base/chat.py} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename src/backend/langflow/{io => components/io/base}/__init__.py (100%) rename src/backend/langflow/{io/schema.py => components/io/base/chat.py} (100%) 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/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 From 6dcad54a385b568ca6facdeab5db6790e47431d4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 23:48:36 -0300 Subject: [PATCH 14/50] Add LCModelComponent class to base/model.py --- .../components/models/base/__init__.py | 0 .../langflow/components/models/base/model.py | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/backend/langflow/components/models/base/__init__.py create mode 100644 src/backend/langflow/components/models/base/model.py 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 From c6b837380b54ce5efa03006e00cfc9d7b796fb30 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Feb 2024 23:48:53 -0300 Subject: [PATCH 15/50] Update model components --- .../components/models/AmazonBedrockModel.py | 14 ++++----- .../components/models/AnthropicModel.py | 14 ++++----- .../components/models/AzureOpenAIModel.py | 14 ++++----- .../models/BaiduQianfanChatModel.py | 14 ++++----- .../components/models/CTransformersModel.py | 13 +++----- .../langflow/components/models/CohereModel.py | 19 ++++++------ .../models/GoogleGenerativeAIModel.py | 30 ++++++++++--------- .../components/models/HuggingFaceModel.py | 15 ++++++---- .../components/models/LlamaCppModel.py | 13 +++----- .../langflow/components/models/OllamaModel.py | 15 ++++------ .../langflow/components/models/OpenAIModel.py | 14 ++++----- .../components/models/VertexAiModel.py | 13 +++----- 12 files changed, 78 insertions(+), 110 deletions(-) diff --git a/src/backend/langflow/components/models/AmazonBedrockModel.py b/src/backend/langflow/components/models/AmazonBedrockModel.py index 761daae65..4ae28e70c 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 = "AmazonBedrock" def build_config(self): return { @@ -65,10 +66,5 @@ class AmazonBedrockComponent(CustomComponent): ) # type: ignore except Exception as e: raise ValueError("Could not connect to AmazonBedrock API.") from e - 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 + + 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 230a5ab2a..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 { @@ -82,10 +83,5 @@ class AnthropicLLM(CustomComponent): ) except Exception as e: raise ValueError("Could not connect to Anthropic API.") from e - 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 + + 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 8931bd6e4..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", @@ -104,10 +105,5 @@ class AzureChatOpenAIComponent(CustomComponent): ) except Exception as e: raise ValueError("Could not connect to AzureOpenAI API.") from e - 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 + + 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 121dd9be6..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 { @@ -99,10 +100,5 @@ class QianfanChatEndpointComponent(CustomComponent): ) except Exception as e: raise ValueError("Could not connect to Baidu Qianfan API.") from e - 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 + + 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 9784f86b1..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" @@ -47,10 +47,5 @@ class CTransformersComponent(CustomComponent): output = CTransformers( model=model, model_file=model_file, model_type=model_type, config=config ) - 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 + + 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 6af3971da..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": { @@ -29,6 +31,10 @@ class CohereComponent(CustomComponent): "show": True, }, "input_value": {"display_name": "Input"}, + "stream": { + "display_name": "Stream", + "info": "Stream the response from the model.", + }, } def build( @@ -37,16 +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, ) - 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 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 40f1d385f..423a66df6 100644 --- a/src/backend/langflow/components/models/GoogleGenerativeAIModel.py +++ b/src/backend/langflow/components/models/GoogleGenerativeAIModel.py @@ -1,13 +1,16 @@ +from typing import Optional + +from langchain_google_genai import ChatGoogleGenerativeAI +from pydantic.v1 import SecretStr + +from langflow.components.models.base.model import LCModelComponent +from langflow.field_typing import RangeSpec, Text -from langflow import CustomComponent -from langflow.field_typing import RangeSpec - - -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" def build_config(self): return { @@ -47,7 +50,11 @@ class GoogleGenerativeAIComponent(CustomComponent): "code": { "advanced": True, }, - "input_value": {e": {"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( @@ -60,6 +67,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, @@ -70,10 +78,4 @@ class GoogleGenerativeAIComponent(CustomComponent): n=n or 1, google_api_key=SecretStr(google_api_key), ) - 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 \ No newline at end of file + 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 3fe97aca1..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 { @@ -25,6 +26,10 @@ class HuggingFaceEndpointsComponent(CustomComponent): }, "code": {"show": False}, "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 00c0ee0f4..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" @@ -140,10 +140,5 @@ class LlamaCppComponent(CustomComponent): verbose=verbose, vocab_only=vocab_only, ) - 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 + + 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 5f0a86289..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 { @@ -255,10 +257,5 @@ class ChatOllamaComponent(CustomComponent): output = ChatOllama(**llm_params) # type: ignore except Exception as e: raise ValueError("Could not initialize Ollama LLM.") from e - 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 + + 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 34a0252be..7a28acee6 100644 --- a/src/backend/langflow/components/models/OpenAIModel.py +++ b/src/backend/langflow/components/models/OpenAIModel.py @@ -2,13 +2,14 @@ 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 { @@ -84,10 +85,5 @@ class OpenAIModelComponent(CustomComponent): api_key=openai_api_key, temperature=temperature, ) - 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 + + 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 dcaa95860..d7eab71ed 100644 --- a/src/backend/langflow/components/models/VertexAiModel.py +++ b/src/backend/langflow/components/models/VertexAiModel.py @@ -2,11 +2,11 @@ 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." @@ -97,10 +97,5 @@ class ChatVertexAIComponent(CustomComponent): top_p=top_p, verbose=verbose, ) - 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 + + return self.get_result(output=output, stream=stream, input_value=input_value) From 257721f86423d021afc219e13c55a70001b127d1 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 00:12:04 -0300 Subject: [PATCH 16/50] Add LCVectorStoreComponent to langflow components --- .../components/vectorstores/base/__init__.py | 0 .../components/vectorstores/base/model.py | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/backend/langflow/components/vectorstores/base/__init__.py create mode 100644 src/backend/langflow/components/vectorstores/base/model.py 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..c6af714d6 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/base/model.py @@ -0,0 +1,27 @@ +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]: + + 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) From ca6baaa0c581591d0f011d1bc7a6bcea1d3f24d4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 00:24:18 -0300 Subject: [PATCH 17/50] Add docstring to search_with_vector_store method --- .../langflow/components/vectorstores/base/model.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/backend/langflow/components/vectorstores/base/model.py b/src/backend/langflow/components/vectorstores/base/model.py index c6af714d6..1cc8b9d88 100644 --- a/src/backend/langflow/components/vectorstores/base/model.py +++ b/src/backend/langflow/components/vectorstores/base/model.py @@ -16,6 +16,20 @@ class LCVectorStoreComponent(CustomComponent): 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): From 796c8428155fea651cd366356df611b5b4636c87 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 00:24:24 -0300 Subject: [PATCH 18/50] Refactor ChromaSearchComponent to inherit from LCVectorStoreComponent --- .../components/vectorstores/ChromaSearch.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/backend/langflow/components/vectorstores/ChromaSearch.py b/src/backend/langflow/components/vectorstores/ChromaSearch.py index 6cb3e89df..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. """ @@ -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) From da3382bbd396615676f5c8e3610694a91ba55579 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 00:24:29 -0300 Subject: [PATCH 19/50] Refactor FAISSComponent to save FAISS index locally --- .../langflow/components/vectorstores/FAISS.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/components/vectorstores/FAISS.py b/src/backend/langflow/components/vectorstores/FAISS.py index dec14f6db..13e5bba35 100644 --- a/src/backend/langflow/components/vectorstores/FAISS.py +++ b/src/backend/langflow/components/vectorstores/FAISS.py @@ -3,24 +3,35 @@ 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.", + }, } 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) From 436966c5d61cb432e465e50c86e2dcace3256a59 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 00:24:55 -0300 Subject: [PATCH 20/50] Add FAISSSearchComponent and PGVectorSearchComponent --- .../langflow/components/vectorstores/FAISS.py | 1 + .../components/vectorstores/FAISSSearch.py | 45 +++++++++++ .../components/vectorstores/pgvectorSearch.py | 75 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/backend/langflow/components/vectorstores/FAISSSearch.py create mode 100644 src/backend/langflow/components/vectorstores/pgvectorSearch.py diff --git a/src/backend/langflow/components/vectorstores/FAISS.py b/src/backend/langflow/components/vectorstores/FAISS.py index 13e5bba35..0cecab8e7 100644 --- a/src/backend/langflow/components/vectorstores/FAISS.py +++ b/src/backend/langflow/components/vectorstores/FAISS.py @@ -21,6 +21,7 @@ class FAISSComponent(CustomComponent): "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( 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/pgvectorSearch.py b/src/backend/langflow/components/vectorstores/pgvectorSearch.py new file mode 100644 index 000000000..f525611bd --- /dev/null +++ b/src/backend/langflow/components/vectorstores/pgvectorSearch.py @@ -0,0 +1,75 @@ +from typing import List, Optional + +from langchain.embeddings.base import Embeddings +from langchain_community.vectorstores.pgvector import PGVector + +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.schema import Record + + +class PGVectorSearchComponent(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 = PGVector.from_existing_index( + embedding=embedding, + collection_name=collection_name, + connection_string=pg_server_url, + ) + + 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 + ) From acc43aebb91193d4052431dc9dff1adbb6e319fa Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Wed, 28 Feb 2024 13:08:57 +0100 Subject: [PATCH 21/50] Fix name of template field on message --- src/frontend/src/components/IOview/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/IOview/index.tsx b/src/frontend/src/components/IOview/index.tsx index a8edfa9b6..0e3f9e7e9 100644 --- a/src/frontend/src/components/IOview/index.tsx +++ b/src/frontend/src/components/IOview/index.tsx @@ -56,7 +56,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element { const chatInputNode = nodes.find((node) => node.id === chatInput?.id); if (chatInputNode) { let newNode = cloneDeep(chatInputNode); - newNode.data.node!.template["message"].value = chatValue; + newNode.data.node!.template["input_value"].value = chatValue; setNode(chatInput!.id, newNode); } for (let i = 0; i < count; i++) { From 9d7f3dda5eb3cff4ac838a0c61033bd320996a96 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 09:20:54 -0300 Subject: [PATCH 22/50] Refactor vector store components --- .../vectorstores/MongoDBAtlasVector.py | 56 ++++++++++++ .../vectorstores/MongoDBAtlasVectorSearch.py | 38 ++++---- .../components/vectorstores/Pinecone.py | 21 ++++- .../components/vectorstores/PineconeSearch.py | 70 ++++++++++++++ .../components/vectorstores/QdrantSearch.py | 91 +++++++++++++++++++ .../components/vectorstores/RedisSearch.py | 77 ++++++++++++++++ .../vectorstores/SupabaseVectorStoreSearch.py | 49 ++++++++++ .../components/vectorstores/Vectara.py | 10 +- .../components/vectorstores/VectaraSearch.py | 64 +++++++++++++ .../components/vectorstores/Weaviate.py | 19 +++- .../components/vectorstores/WeaviateSearch.py | 82 +++++++++++++++++ .../components/vectorstores/pgvectorSearch.py | 10 +- src/backend/langflow/config.yaml | 19 +--- 13 files changed, 556 insertions(+), 50 deletions(-) create mode 100644 src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py create mode 100644 src/backend/langflow/components/vectorstores/PineconeSearch.py create mode 100644 src/backend/langflow/components/vectorstores/QdrantSearch.py create mode 100644 src/backend/langflow/components/vectorstores/RedisSearch.py create mode 100644 src/backend/langflow/components/vectorstores/SupabaseVectorStoreSearch.py create mode 100644 src/backend/langflow/components/vectorstores/VectaraSearch.py create mode 100644 src/backend/langflow/components/vectorstores/WeaviateSearch.py diff --git a/src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py b/src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py new file mode 100644 index 000000000..de5533b0a --- /dev/null +++ b/src/backend/langflow/components/vectorstores/MongoDBAtlasVector.py @@ -0,0 +1,56 @@ +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." + ) + + 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/QdrantSearch.py b/src/backend/langflow/components/vectorstores/QdrantSearch.py new file mode 100644 index 000000000..46e2766fd --- /dev/null +++ b/src/backend/langflow/components/vectorstores/QdrantSearch.py @@ -0,0 +1,91 @@ +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." + + 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..493632fe9 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/SupabaseVectorStoreSearch.py @@ -0,0 +1,49 @@ +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." + + 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..b5360ffd2 100644 --- a/src/backend/langflow/components/vectorstores/Vectara.py +++ b/src/backend/langflow/components/vectorstores/Vectara.py @@ -8,12 +8,15 @@ 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 field_config = { "vectara_customer_id": { @@ -26,7 +29,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..c90148d1e --- /dev/null +++ b/src/backend/langflow/components/vectorstores/VectaraSearch.py @@ -0,0 +1,64 @@ +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 + 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..2d7001074 --- /dev/null +++ b/src/backend/langflow/components/vectorstores/WeaviateSearch.py @@ -0,0 +1,82 @@ +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 + 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/pgvectorSearch.py b/src/backend/langflow/components/vectorstores/pgvectorSearch.py index f525611bd..00e291e76 100644 --- a/src/backend/langflow/components/vectorstores/pgvectorSearch.py +++ b/src/backend/langflow/components/vectorstores/pgvectorSearch.py @@ -1,13 +1,13 @@ from typing import List, Optional from langchain.embeddings.base import Embeddings -from langchain_community.vectorstores.pgvector import PGVector from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.components.vectorstores.pgvector import PGVectorComponent from langflow.schema import Record -class PGVectorSearchComponent(LCVectorStoreComponent): +class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent): """ A custom component for implementing a Vector Store using PostgreSQL. """ @@ -60,14 +60,12 @@ class PGVectorSearchComponent(LCVectorStoreComponent): Returns: - VectorStore: The Vector Store object. """ - try: - vector_store = PGVector.from_existing_index( + vector_store = super().build( embedding=embedding, + pg_server_url=pg_server_url, collection_name=collection_name, - connection_string=pg_server_url, ) - except Exception as e: raise RuntimeError(f"Failed to build PGVector: {e}") return self.search_with_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: "" From 609744332d16e64c05f640dbc60436aed528f8c2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 09:57:41 -0300 Subject: [PATCH 23/50] Add linting and testing workflows for backend code (#1478) * Add paths to trigger workflow on specific file changes * Add linting workflow for backend code --- .github/workflows/lint.yml | 8 ++++++++ .github/workflows/test.yml | 8 ++++++++ 2 files changed, 16 insertions(+) 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" From 43b3b082a5b9c58ae19647f4348e6bdc6938839e Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa <72977554+Cristhianzl@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:01:09 -0300 Subject: [PATCH 24/50] Fix vectara resolution error (#1475) * add new vectara icon * remove console.log --- .../src/CustomNodes/GenericNode/index.tsx | 2 +- .../src/icons/VectaraIcon/Vectara.jsx | 1153 ++--------------- .../src/icons/VectaraIcon/vectara.svg | 332 ++--- 3 files changed, 162 insertions(+), 1325 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 2ddd49b8f..0d344bd65 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -186,7 +186,7 @@ export default function GenericNode({
{iconNodeRender()} diff --git a/src/frontend/src/icons/VectaraIcon/Vectara.jsx b/src/frontend/src/icons/VectaraIcon/Vectara.jsx index 0566f82eb..378dc6964 100644 --- a/src/frontend/src/icons/VectaraIcon/Vectara.jsx +++ b/src/frontend/src/icons/VectaraIcon/Vectara.jsx @@ -1,1074 +1,83 @@ -const SvgVectara = (props) => ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +export const SvgVectara = (props) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); export default SvgVectara; diff --git a/src/frontend/src/icons/VectaraIcon/vectara.svg b/src/frontend/src/icons/VectaraIcon/vectara.svg index 1faafd320..c111a3aeb 100644 --- a/src/frontend/src/icons/VectaraIcon/vectara.svg +++ b/src/frontend/src/icons/VectaraIcon/vectara.svg @@ -1,252 +1,80 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 109b5c8690b36ebb5c92e44d7ca913bbec1e01aa Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 10:02:00 -0300 Subject: [PATCH 25/50] Remove unused chat and prompt classes (#1474) --- src/backend/langflow/utils/chat.py | 34 ---------------- src/backend/langflow/utils/prompt.py | 58 ---------------------------- 2 files changed, 92 deletions(-) delete mode 100644 src/backend/langflow/utils/chat.py delete mode 100644 src/backend/langflow/utils/prompt.py diff --git a/src/backend/langflow/utils/chat.py b/src/backend/langflow/utils/chat.py deleted file mode 100644 index e1621bdab..000000000 --- a/src/backend/langflow/utils/chat.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Any, Callable, Optional, Union - -from langchain_core.prompts import PromptTemplate as LCPromptTemplate -from langflow.utils.prompt import GenericPromptTemplate -from llama_index.prompts import PromptTemplate as LIPromptTemplate - -PromptTemplate = Union[LCPromptTemplate, LIPromptTemplate] - - -class ChatDefinition: - def __init__( - self, - func: Callable, - inputs: list[str], - output_key: Optional[str] = None, - prompt_template: Optional[PromptTemplate] = None, - ): - self.func = func - self.input_keys = inputs - self.output_key = output_key - self.prompt_template = prompt_template - - @classmethod - def from_prompt_template(cls, prompt_template: PromptTemplate, func: Callable, output_key: Optional[str] = None): - prompt = GenericPromptTemplate(prompt_template) - return cls( - func=func, - inputs=prompt.input_keys, - output_key=output_key, - prompt_template=prompt_template, - ) - - def __call__(self, inputs: dict, callbacks: Optional[Any] = None) -> dict: - return self.func(inputs, callbacks) diff --git a/src/backend/langflow/utils/prompt.py b/src/backend/langflow/utils/prompt.py deleted file mode 100644 index 871193f45..000000000 --- a/src/backend/langflow/utils/prompt.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import Any, Union - -from langchain_core.prompts import PromptTemplate as LCPromptTemplate -from llama_index.prompts import PromptTemplate as LIPromptTemplate - -PromptTemplateTypes = Union[LCPromptTemplate, LIPromptTemplate] - - -class GenericPromptTemplate: - def __init__(self, prompt_template: PromptTemplateTypes): - object.__setattr__(self, "prompt_template", prompt_template) - - @property - def input_keys(self): - prompt_template = object.__getattribute__(self, "prompt_template") - if isinstance(prompt_template, LCPromptTemplate): - return prompt_template.input_variables - elif isinstance(prompt_template, LIPromptTemplate): - return prompt_template.template_vars - else: - raise TypeError(f"Unknown prompt template type {type(prompt_template)}") - - def to_lc_prompt(self): - prompt_template = object.__getattribute__(self, "prompt_template") - if isinstance(prompt_template, LCPromptTemplate): - return prompt_template - elif isinstance(prompt_template, LIPromptTemplate): - return LCPromptTemplate.from_template(prompt_template.get_template()) - else: - raise TypeError(f"Unknown prompt template type {type(prompt_template)}") - - def to_li_prompt(self): - prompt_template = object.__getattribute__(self, "prompt_template") - if isinstance(prompt_template, LIPromptTemplate): - return prompt_template - elif isinstance(prompt_template, LCPromptTemplate): - return LIPromptTemplate(template=prompt_template.template) - else: - raise TypeError(f"Unknown prompt template type {type(prompt_template)}") - - def __or__(self, other): - prompt_template = object.__getattribute__(self, "prompt_template") - if isinstance(prompt_template, LIPromptTemplate): - return self.to_lc_prompt() | other - else: - raise TypeError(f"Unknown prompt template type {type(other)}") - - def __getattribute__(self, name: str) -> Any: - if name in { - "input_keys", - "to_lc_prompt", - "to_li_prompt", - "__or__", - "prompt_template", - }: - return object.__getattribute__(self, name) - prompt_template = object.__getattribute__(self, "prompt_template") - return getattr(prompt_template, name) From c348b4204afebaac814d2430d8222cfde544f00a Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa <72977554+Cristhianzl@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:29:52 -0300 Subject: [PATCH 26/50] Change the color on str output, change X icon on Notification tab, change the icon on nodeToolbar (Code) (#1473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 fix(GenericNode): simplify return statement for build status classes to improve code readability 🔥 chore(GenericNode): remove console.log statement used for debugging 🔥 chore(alertDropDown): remove unused import 🔥 chore(nodeToolbarComponent): remove unused import 🔥 chore(styleUtils): remove unused color definition * 🎨 style(styleUtils.ts): update color for 'str' key in nodeColors object to match the color used in other keys * 🔥 refactor(GenericNode/index.tsx): remove unnecessary console.log statement for data.node?.template --- .../src/alerts/alertDropDown/index.tsx | 7 ++--- .../components/nodeToolbarComponent/index.tsx | 26 +++++++++---------- src/frontend/src/utils/styleUtils.ts | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) 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/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index a0136ba85..f7ee2eba4 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,5 +1,6 @@ import _, { cloneDeep } from "lodash"; import { useEffect, useState } from "react"; +import { useUpdateNodeInternals } from "reactflow"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import CodeAreaComponent from "../../../../components/codeAreaComponent"; import IconComponent from "../../../../components/genericIconComponent"; @@ -26,7 +27,6 @@ import { updateFlowPosition, } from "../../../../utils/reactflowUtils"; import { classNames, cn } from "../../../../utils/utils"; -import { useUpdateNodeInternals } from "reactflow"; export default function NodeToolbarComponent({ data, @@ -89,7 +89,9 @@ export default function NodeToolbarComponent({ }, [showModalAdvanced]); const updateNodeInternals = useUpdateNodeInternals(); - const setLastCopiedSelection = useFlowStore(state => state.setLastCopiedSelection); + const setLastCopiedSelection = useFlowStore( + (state) => state.setLastCopiedSelection + ); useEffect(() => { setFlowComponent(createFlowComponent(cloneDeep(data), version)); }, [ @@ -144,8 +146,8 @@ export default function NodeToolbarComponent({ deleteNode(data.id); break; case "copy": - const node = nodes.filter(node => node.id === data.id) - setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] }) + const node = nodes.filter((node) => node.id === data.id); + setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] }); } }; @@ -233,7 +235,7 @@ export default function NodeToolbarComponent({ id={"code-input-node-toolbar-" + name} /> - + ) : ( @@ -371,13 +373,11 @@ export default function NodeToolbarComponent({ className="relative top-0.5 mr-2 h-4 w-4 " />{" "} Copy{" "} - - - C - + + C {hasStore && ( @@ -450,7 +450,7 @@ export default function NodeToolbarComponent({ diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 62e1c6ec7..4abcecdaf 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -219,7 +219,7 @@ export const nodeColors: { [char: string]: string } = { wrappers: "#E6277A", utilities: "#31A3CC", output_parsers: "#E6A627", - str: "#049524", + str: "#31a3cc", retrievers: "#e6b25a", unknown: "#9CA3AF", custom_components: "#ab11ab", From 5c210f609e249cd8017cd24aa04bed8d8272f580 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 10:37:01 -0300 Subject: [PATCH 27/50] Refactor get_files to exclude folders inside the component path --- .../directory_reader/directory_reader.py | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) 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 From 8aaa0ee91a10c427d22c5ed43798b003681b25de Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 10:37:13 -0300 Subject: [PATCH 28/50] Refactor directory_reader utils.py --- .../custom/directory_reader/utils.py | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) 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 From f09ebeba340428bff12a97cb1d82dcfbdff85a64 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 11:03:55 -0300 Subject: [PATCH 29/50] Add support for input values in build_vertex API --- src/backend/langflow/api/v1/chat.py | 8 ++++++-- src/backend/langflow/api/v1/schemas.py | 4 ++++ src/backend/langflow/graph/vertex/base.py | 15 +++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 91b4bcd65..85a3a7abe 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, @@ -139,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() @@ -163,7 +167,7 @@ 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) + await vertex.build(user_id=current_user.id, inputs=inputs.model_dump()) if vertex.result is not None: params = vertex._built_object_repr() 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/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index b917889a4..c5b993f27 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -2,13 +2,16 @@ import ast import inspect import types from enum import Enum -from typing import (TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, - Optional) +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional from loguru import logger -from langflow.graph.schema import (INPUT_COMPONENTS, OUTPUT_COMPONENTS, - InterfaceComponentTypes, ResultData) +from langflow.graph.schema import ( + INPUT_COMPONENTS, + OUTPUT_COMPONENTS, + InterfaceComponentTypes, + ResultData, +) from langflow.graph.utils import UnbuiltObject, UnbuiltResult from langflow.graph.vertex.utils import generate_result from langflow.interface.initialize import loading @@ -608,6 +611,7 @@ class Vertex: async def build( self, user_id=None, + inputs: Optional[Dict[str, Any]] = None, requester: Optional["Vertex"] = None, **kwargs, ) -> Any: @@ -620,6 +624,9 @@ class Vertex: return self.get_requester_result(requester) self._reset() + if inputs and self.is_input: + self.update_raw_params(inputs) + # Run steps for step in self.steps: if step not in self.steps_ran: From 11bf5ee4604f3c42566b83ca7aa71d1388c23d32 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 11:07:16 -0300 Subject: [PATCH 30/50] Refactor build_vertex function to handle inputs dictionary correctly --- src/backend/langflow/api/v1/chat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 85a3a7abe..a4937789e 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -167,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=inputs.model_dump()) + 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() From 04bbf4eaf0b54ae05df578b2a256148890c33b68 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Wed, 28 Feb 2024 17:20:57 +0100 Subject: [PATCH 31/50] Made vertices be retrieved when opening chat or when changing anything. Added chat input in request. --- .../src/CustomNodes/GenericNode/index.tsx | 2 +- .../src/components/IOInputField/index.tsx | 8 +- .../src/components/IOOutputView/index.tsx | 4 +- src/frontend/src/components/IOview/index.tsx | 83 ++++++++----- .../chatComponent/buildTrigger/index.tsx | 4 +- .../newChatView/chatInput/index.tsx | 18 +-- src/frontend/src/controllers/API/api.tsx | 2 +- src/frontend/src/controllers/API/index.ts | 5 +- src/frontend/src/stores/flowStore.ts | 82 ++++++++----- src/frontend/src/stores/flowsManagerStore.ts | 3 +- src/frontend/src/types/zustand/flow/index.ts | 8 +- src/frontend/src/utils/buildUtils.ts | 115 +++++++++++------- 12 files changed, 203 insertions(+), 131 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 5632f3c7a..a536ec6d6 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -465,7 +465,7 @@ export default function GenericNode({ if (buildStatus === BuildStatus.BUILDING || isBuilding) return; setValidationStatus(null); - buildFlow(data.id); + buildFlow({nodeId: data.id}); }} >
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({