diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index 05b637060..31d40a5c1 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -214,11 +214,13 @@ async def simplified_run_flow( return result except ValueError as exc: - end_time = time.perf_counter() background_tasks.add_task( telemetry_service.log_package_run, RunPayload( - runIsWebhook=False, runSeconds=int(end_time - start_time), runSuccess=False, runErrorMessage=str(exc) + runIsWebhook=False, + runSeconds=int(time.perf_counter() - start_time), + runSuccess=False, + runErrorMessage=str(exc), ), ) if "badly formed hexadecimal UUID string" in str(exc): @@ -234,7 +236,10 @@ async def simplified_run_flow( background_tasks.add_task( telemetry_service.log_package_run, RunPayload( - runIsWebhook=False, runSeconds=int(end_time - start_time), runSuccess=False, runErrorMessage=str(exc) + runIsWebhook=False, + runSeconds=int(time.perf_counter() - start_time), + runSuccess=False, + runErrorMessage=str(exc), ), ) raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc diff --git a/src/backend/base/langflow/base/textsplitters/model.py b/src/backend/base/langflow/base/textsplitters/model.py index a4a58612b..624222fb2 100644 --- a/src/backend/base/langflow/base/textsplitters/model.py +++ b/src/backend/base/langflow/base/textsplitters/model.py @@ -1,7 +1,7 @@ from abc import abstractmethod from typing import Any -from langchain_text_splitters import TextSplitter +from langchain_text_splitters import TextSplitter from langflow.custom import Component from langflow.io import Output @@ -29,7 +29,7 @@ class LCTextSplitterComponent(Component): documents = [] if not isinstance(data_input, list): - data_input: list[Any] = [data_input] + data_input = [data_input] for _input in data_input: if isinstance(_input, Data): diff --git a/src/backend/base/langflow/components/textsplitters/CharacterTextSplitter.py b/src/backend/base/langflow/components/textsplitters/CharacterTextSplitter.py index 69ba546a1..1b70fcb3b 100644 --- a/src/backend/base/langflow/components/textsplitters/CharacterTextSplitter.py +++ b/src/backend/base/langflow/components/textsplitters/CharacterTextSplitter.py @@ -1,10 +1,9 @@ -from typing import List, Any +from typing import Any from langchain_text_splitters import CharacterTextSplitter, TextSplitter from langflow.base.textsplitters.model import LCTextSplitterComponent -from langflow.inputs import IntInput, DataInput, MessageTextInput -from langflow.schema import Data +from langflow.inputs import DataInput, IntInput, MessageTextInput from langflow.utils.util import unescape_string @@ -53,27 +52,3 @@ class CharacterTextSplitterComponent(LCTextSplitterComponent): chunk_size=self.chunk_size, separator=separator, ) - - def build( - self, - inputs: List[Data], - chunk_overlap: int = 200, - chunk_size: int = 1000, - separator: str = "\n", - ) -> List[Data]: - # separator may come escaped from the frontend - separator = unescape_string(separator) - documents = [] - for _input in inputs: - if isinstance(_input, Data): - documents.append(_input.to_lc_document()) - else: - documents.append(_input) - docs = CharacterTextSplitter( - chunk_overlap=chunk_overlap, - chunk_size=chunk_size, - separator=separator, - ).split_documents(documents) - data = self.to_data(docs) - self.status = data - return data diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index cd761bbff..253dc8484 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -14,7 +14,7 @@ from langflow.graph.edge.base import ContractEdge from langflow.graph.graph.constants import lazy_load_vertex_dict from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager from langflow.graph.graph.state_manager import GraphStateManager -from langflow.graph.graph.utils import process_flow +from langflow.graph.graph.utils import find_start_component_id, process_flow from langflow.graph.schema import InterfaceComponentTypes, RunOutputs from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.types import InterfaceVertex, StateVertex @@ -335,9 +335,8 @@ class Graph: logger.exception(exc) try: - start_component_id = next( - (vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None - ) + # Prioritize the webhook component if it exists + start_component_id = find_start_component_id(self._is_input_vertices) await self.process(start_component_id=start_component_id, fallback_to_env_vars=fallback_to_env_vars) self.increment_run_count() except Exception as exc: @@ -350,6 +349,8 @@ class Graph: # Get the outputs vertex_outputs = [] for vertex in self.vertices: + if not vertex._built: + continue if vertex is None: raise ValueError(f"Vertex {vertex_id} not found") diff --git a/src/backend/base/langflow/graph/graph/utils.py b/src/backend/base/langflow/graph/graph/utils.py index a3299739e..a4a643177 100644 --- a/src/backend/base/langflow/graph/graph/utils.py +++ b/src/backend/base/langflow/graph/graph/utils.py @@ -1,5 +1,24 @@ -from collections import deque import copy +from collections import deque + +PRIORITY_LIST_OF_INPUTS = ["webhook", "chat"] + + +def find_start_component_id(vertices): + """ + Finds the component ID from a list of vertices based on a priority list of input types. + + Args: + vertices (list): A list of vertex IDs. + + Returns: + str or None: The component ID that matches the highest priority input type, or None if no match is found. + """ + for input_type_str in PRIORITY_LIST_OF_INPUTS: + component_id = next((vertex_id for vertex_id in vertices if input_type_str in vertex_id.lower()), None) + if component_id: + return component_id + return None def find_last_node(nodes, edges): diff --git a/src/backend/base/langflow/graph/schema.py b/src/backend/base/langflow/graph/schema.py index 40b707fbd..ee6551cd0 100644 --- a/src/backend/base/langflow/graph/schema.py +++ b/src/backend/base/langflow/graph/schema.py @@ -54,6 +54,7 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta): TextInput = "TextInput" TextOutput = "TextOutput" DataOutput = "DataOutput" + WebhookInput = "Webhook" def __contains__(cls, item): try: @@ -69,6 +70,7 @@ RECORDS_COMPONENTS = [InterfaceComponentTypes.DataOutput] INPUT_COMPONENTS = [ InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.TextInput, + InterfaceComponentTypes.WebhookInput, ] OUTPUT_COMPONENTS = [ InterfaceComponentTypes.ChatOutput, diff --git a/tests/data/WebhookTest.json b/tests/data/WebhookTest.json index fd63c5346..b424b137d 100644 --- a/tests/data/WebhookTest.json +++ b/tests/data/WebhookTest.json @@ -1,228 +1 @@ -{ - "id": "40b2ae66-384b-4978-85ab-f79706287a1a", - "data": { - "nodes": [ - { - "id": "CustomComponent-xbSW2", - "type": "genericNode", - "position": { - "x": 888.0012384532345, - "y": 274.9520639008431 - }, - "data": { - "type": "CustomComponent", - "node": { - "template": { - "_type": "Component", - "code": { - "type": "code", - "required": true, - "placeholder": "", - "list": false, - "show": true, - "multiline": true, - "value": "# from langflow.field_typing import Data\nfrom langflow.custom import Component\nfrom langflow.io import StrInput\nfrom langflow.schema import Data\nfrom langflow.io import Output\n\n\nclass CustomComponent(Component):\n display_name = \"Custom Component\"\n description = \"Use as a template to create your own component.\"\n documentation: str = \"http://docs.langflow.org/components/custom\"\n icon = \"custom_components\"\n\n inputs = [\n StrInput(name=\"input_value\", display_name=\"Input Value\", value=\"Hello, World!\", input_types=[\"Data\"]),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n def build_output(self) -> Data:\n if isinstance(self.input_value, Data):\n data = self.input_value\n else:\n data = Data(value=self.input_value)\n \n if \"path\" in data:\n path = self.resolve_path(data.path)\n path_obj = Path(path)\n with open(path, \"w\") as f:\n f.write(data.model_dump())\n self.status = data\n return data\n", - "fileTypes": [], - "file_path": "", - "password": false, - "name": "code", - "advanced": true, - "dynamic": true, - "info": "", - "load_from_db": false, - "title_case": false - }, - "input_value": { - "load_from_db": false, - "list": false, - "required": false, - "placeholder": "", - "show": true, - "value": "", - "name": "input_value", - "display_name": "Input Value", - "advanced": false, - "input_types": [ - "Data" - ], - "dynamic": false, - "info": "", - "title_case": false, - "type": "str" - } - }, - "description": "Use as a template to create your own component.", - "icon": "custom_components", - "base_classes": [ - "Data" - ], - "display_name": "Custom Component", - "documentation": "http://docs.langflow.org/components/custom", - "custom_fields": {}, - "output_types": [], - "pinned": false, - "conditional_paths": [], - "frozen": false, - "outputs": [ - { - "types": [ - "Data" - ], - "selected": "Data", - "name": "output", - "display_name": "Output", - "method": "build_output", - "value": "__UNDEFINED__", - "cache": true - } - ], - "field_order": [ - "input_value" - ], - "beta": false, - "edited": true - }, - "id": "CustomComponent-xbSW2", - "description": "Use as a template to create your own component.", - "display_name": "Custom Component" - }, - "selected": false, - "width": 384, - "height": 337, - "positionAbsolute": { - "x": 888.0012384532345, - "y": 274.9520639008431 - }, - "dragging": false - }, - { - "id": "Webhook-eeCWO", - "type": "genericNode", - "position": { - "x": 418, - "y": 270.2890625 - }, - "data": { - "type": "Webhook", - "node": { - "template": { - "_type": "Component", - "code": { - "type": "code", - "required": true, - "placeholder": "", - "list": false, - "show": true, - "multiline": true, - "value": "import json\n\nfrom langflow.custom import Component\nfrom langflow.io import MultilineInput\nfrom langflow.schema import Data\nfrom langflow.io import Output\n\n\nclass WebhookComponent(Component):\n display_name = \"Webhook Input\"\n description = \"Defines a webhook input for the flow.\"\n\n inputs = [\n MultilineInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"Use this field to quickly test the webhook component by providing a JSON payload.\",\n multiline=True,\n )\n ]\n outputs = [\n Output(display_name=\"Data\", name=\"output_data\", method=\"build_data\"),\n ]\n\n def build_data(self) -> Data:\n message = \"\"\n if not self.data:\n self.status = \"No data provided.\"\n return Data(data={})\n try:\n body = json.loads(self.data or \"{}\")\n except json.JSONDecodeError:\n body = {\"payload\": self.data}\n message = f\"Invalid JSON payload. Please check the format.\\n\\n{self.data}\"\n data = Data(data=body)\n if not message:\n message = data\n self.status = message\n return data\n", - "fileTypes": [], - "file_path": "", - "password": false, - "name": "code", - "advanced": true, - "dynamic": true, - "info": "", - "load_from_db": false, - "title_case": false - }, - "data": { - "multiline": true, - "load_from_db": false, - "list": false, - "required": false, - "placeholder": "", - "show": true, - "value": "{\"test\":1}", - "name": "data", - "display_name": "Data", - "advanced": false, - "input_types": [ - "Message" - ], - "dynamic": false, - "info": "Use this field to quickly test the webhook component by providing a JSON payload.", - "title_case": false, - "type": "str" - } - }, - "description": "Defines a webhook input for the flow.", - "base_classes": [ - "Data" - ], - "display_name": "Webhook Input", - "documentation": "", - "custom_fields": {}, - "output_types": [], - "pinned": false, - "conditional_paths": [], - "frozen": false, - "outputs": [ - { - "types": [ - "Data" - ], - "selected": "Data", - "name": "output_data", - "display_name": "Data", - "method": "build_data", - "value": "__UNDEFINED__", - "cache": true, - "hidden": false - } - ], - "field_order": [ - "data" - ], - "beta": false - }, - "id": "Webhook-eeCWO" - }, - "selected": true, - "width": 384, - "height": 309, - "dragging": true, - "positionAbsolute": { - "x": 418, - "y": 270.2890625 - } - } - ], - "edges": [ - { - "source": "Webhook-eeCWO", - "sourceHandle": "{œdataTypeœ:œWebhookœ,œidœ:œWebhook-eeCWOœ,œnameœ:œoutput_dataœ,œoutput_typesœ:[œDataœ]}", - "target": "CustomComponent-xbSW2", - "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-xbSW2œ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}", - "data": { - "targetHandle": { - "fieldName": "input_value", - "id": "CustomComponent-xbSW2", - "inputTypes": [ - "Data" - ], - "type": "str" - }, - "sourceHandle": { - "dataType": "Webhook", - "id": "Webhook-eeCWO", - "name": "output_data", - "output_types": [ - "Data" - ] - } - }, - "id": "reactflow__edge-Webhook-eeCWO{œdataTypeœ:œWebhookœ,œidœ:œWebhook-eeCWOœ,œnameœ:œoutput_dataœ,œoutput_typesœ:[œDataœ]}-CustomComponent-xbSW2{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-xbSW2œ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}" - } - ], - "viewport": { - "x": -243, - "y": -16, - "zoom": 1 - } - }, - "description": "The Power of Language at Your Fingertips.", - "name": "Webhook Test", - "last_tested_version": "1.0.0a59", - "endpoint_name": "webhook-test", - "is_component": false -} \ No newline at end of file +{"id":"b00c375e-c858-42b5-a352-561d3f40bd15","data":{"nodes":[{"id":"CustomComponent-aF0h1","type":"genericNode","position":{"x":888.0012384532345,"y":274.9520639008431},"data":{"type":"CustomComponent","node":{"template":{"_type":"Component","code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"# from langflow.field_typing import Data\nfrom langflow.custom import Component\nfrom langflow.io import StrInput\nfrom langflow.schema import Data\nfrom langflow.io import Output\n\n\nclass CustomComponent(Component):\n display_name = \"Custom Component\"\n description = \"Use as a template to create your own component.\"\n documentation: str = \"http://docs.langflow.org/components/custom\"\n icon = \"custom_components\"\n\n inputs = [\n StrInput(name=\"input_value\", display_name=\"Input Value\", value=\"Hello, World!\", input_types=[\"Data\"]),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n def build_output(self) -> Data:\n if isinstance(self.input_value, Data):\n data = self.input_value\n else:\n data = Data(value=self.input_value)\n \n if \"path\" in data:\n path = self.resolve_path(data.path)\n path_obj = Path(path)\n with open(path, \"w\") as f:\n f.write(data.model_dump())\n self.status = data\n return data\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"input_value":{"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"","name":"input_value","display_name":"Input Value","advanced":false,"input_types":["Data"],"dynamic":false,"info":"","title_case":false,"type":"str"}},"description":"Use as a template to create your own component.","icon":"custom_components","base_classes":["Data"],"display_name":"Custom Component","documentation":"http://docs.langflow.org/components/custom","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Data"],"selected":"Data","name":"output","display_name":"Output","method":"build_output","value":"__UNDEFINED__","cache":true}],"field_order":["input_value"],"beta":false,"edited":true},"id":"CustomComponent-aF0h1","description":"Use as a template to create your own component.","display_name":"Custom Component"},"selected":false,"width":384,"height":337,"positionAbsolute":{"x":888.0012384532345,"y":274.9520639008431},"dragging":false},{"id":"Webhook-BeRcd","type":"genericNode","position":{"x":418,"y":270.2890625},"data":{"type":"Webhook","node":{"template":{"_type":"Component","code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"import json\n\nfrom langflow.custom import Component\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema import Data\n\n\nclass WebhookComponent(Component):\n display_name = \"Webhook Input\"\n description = \"Defines a webhook input for the flow.\"\n name = \"Webhook\"\n\n inputs = [\n MultilineInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"Use this field to quickly test the webhook component by providing a JSON payload.\",\n )\n ]\n outputs = [\n Output(display_name=\"Data\", name=\"output_data\", method=\"build_data\"),\n ]\n\n def build_data(self) -> Data:\n message: str | Data = \"\"\n if not self.data:\n self.status = \"No data provided.\"\n return Data(data={})\n try:\n body = json.loads(self.data or \"{}\")\n except json.JSONDecodeError:\n body = {\"payload\": self.data}\n message = f\"Invalid JSON payload. Please check the format.\\n\\n{self.data}\"\n data = Data(data=body)\n if not message:\n message = data\n self.status = message\n return data\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"data":{"trace_as_input":true,"multiline":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"{\"test\":1}","name":"data","display_name":"Data","advanced":false,"input_types":["Message"],"dynamic":false,"info":"Use this field to quickly test the webhook component by providing a JSON payload.","title_case":false,"type":"str"}},"description":"Defines a webhook input for the flow.","base_classes":["Data"],"display_name":"Webhook Input","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Data"],"selected":"Data","name":"output_data","display_name":"Data","method":"build_data","value":"__UNDEFINED__","cache":true}],"field_order":["data"],"beta":false,"edited":false},"id":"Webhook-BeRcd","description":"Defines a webhook input for the flow.","display_name":"Webhook Input"},"selected":false,"width":384,"height":309,"dragging":true,"positionAbsolute":{"x":418,"y":270.2890625}},{"id":"ChatInput-QivBB","type":"genericNode","position":{"x":419.7235078147726,"y":646.9863203129902},"data":{"type":"ChatInput","node":{"template":{"_type":"Component","files":{"trace_as_metadata":true,"file_path":"","fileTypes":["txt","md","mdx","csv","json","yaml","yml","xml","html","htm","pdf","docx","py","sh","sql","js","ts","tsx","jpg","jpeg","png","bmp","image"],"list":true,"required":false,"placeholder":"","show":true,"value":"","name":"files","display_name":"Files","advanced":true,"dynamic":false,"info":"Files to be sent with the message.","title_case":false,"type":"file"},"code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[\"Machine\", \"User\"],\n value=\"User\",\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=\"User\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and isinstance(message.text, str):\n self.store_message(message)\n self.message.value = message\n\n self.status = message\n return message\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"input_value":{"trace_as_input":true,"multiline":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"Should not run","name":"input_value","display_name":"Text","advanced":false,"input_types":["Message"],"dynamic":false,"info":"Message to be passed as input.","title_case":false,"type":"str"},"sender":{"trace_as_metadata":true,"options":["Machine","User"],"required":false,"placeholder":"","show":true,"value":"User","name":"sender","display_name":"Sender Type","advanced":true,"dynamic":false,"info":"Type of sender.","title_case":false,"type":"str"},"sender_name":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"User","name":"sender_name","display_name":"Sender Name","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Name of the sender.","title_case":false,"type":"str"},"session_id":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"","name":"session_id","display_name":"Session ID","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Session ID for the message.","title_case":false,"type":"str"}},"description":"Get chat inputs from the Playground.","icon":"ChatInput","base_classes":["Message"],"display_name":"Chat Input","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"message","display_name":"Message","method":"message_response","value":"__UNDEFINED__","cache":true,"hidden":false}],"field_order":["input_value","sender","sender_name","session_id","files"],"beta":false,"edited":false},"id":"ChatInput-QivBB"},"selected":false,"width":384,"height":309,"positionAbsolute":{"x":419.7235078147726,"y":646.9863203129902},"dragging":false},{"id":"ChatOutput-mN2VY","type":"genericNode","position":{"x":884.7327265656637,"y":662.4287265670896},"data":{"type":"ChatOutput","node":{"template":{"_type":"Component","code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.base.io.chat import ChatComponent\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[\"Machine\", \"User\"],\n value=\"Machine\",\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\", display_name=\"Sender Name\", info=\"Name of the sender.\", value=\"AI\", advanced=True\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and isinstance(message.text, str):\n self.store_message(message)\n self.message.value = message\n\n self.status = message\n return message\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"data_template":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"{text}","name":"data_template","display_name":"Data Template","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.","title_case":false,"type":"str"},"input_value":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"","name":"input_value","display_name":"Text","advanced":false,"input_types":["Message"],"dynamic":false,"info":"Message to be passed as output.","title_case":false,"type":"str"},"sender":{"trace_as_metadata":true,"options":["Machine","User"],"required":false,"placeholder":"","show":true,"value":"Machine","name":"sender","display_name":"Sender Type","advanced":true,"dynamic":false,"info":"Type of sender.","title_case":false,"type":"str"},"sender_name":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"AI","name":"sender_name","display_name":"Sender Name","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Name of the sender.","title_case":false,"type":"str"},"session_id":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"value":"","name":"session_id","display_name":"Session ID","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Session ID for the message.","title_case":false,"type":"str"}},"description":"Display a chat message in the Playground.","icon":"ChatOutput","base_classes":["Message"],"display_name":"Chat Output","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"message","display_name":"Message","method":"message_response","value":"__UNDEFINED__","cache":true}],"field_order":["input_value","sender","sender_name","session_id","data_template"],"beta":false,"edited":false},"id":"ChatOutput-mN2VY"},"selected":true,"width":384,"height":309,"positionAbsolute":{"x":884.7327265656637,"y":662.4287265670896},"dragging":false}],"edges":[{"source":"Webhook-BeRcd","sourceHandle":"{œdataTypeœ:œWebhookœ,œidœ:œWebhook-BeRcdœ,œnameœ:œoutput_dataœ,œoutput_typesœ:[œDataœ]}","target":"CustomComponent-aF0h1","targetHandle":"{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-aF0h1œ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}","data":{"targetHandle":{"fieldName":"input_value","id":"CustomComponent-aF0h1","inputTypes":["Data"],"type":"str"},"sourceHandle":{"dataType":"Webhook","id":"Webhook-BeRcd","name":"output_data","output_types":["Data"]}},"id":"reactflow__edge-Webhook-BeRcd{œdataTypeœ:œWebhookœ,œidœ:œWebhook-BeRcdœ,œnameœ:œoutput_dataœ,œoutput_typesœ:[œDataœ]}-CustomComponent-aF0h1{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-aF0h1œ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}","className":""},{"source":"ChatInput-QivBB","sourceHandle":"{œdataTypeœ:œChatInputœ,œidœ:œChatInput-QivBBœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}","target":"ChatOutput-mN2VY","targetHandle":"{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-mN2VYœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","data":{"targetHandle":{"fieldName":"input_value","id":"ChatOutput-mN2VY","inputTypes":["Message"],"type":"str"},"sourceHandle":{"dataType":"ChatInput","id":"ChatInput-QivBB","name":"message","output_types":["Message"]}},"id":"reactflow__edge-ChatInput-QivBB{œdataTypeœ:œChatInputœ,œidœ:œChatInput-QivBBœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-mN2VY{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-mN2VYœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}"}],"viewport":{"x":-97.805027048581,"y":24.539953485641945,"zoom":0.7695036100804309}},"description":"The Power of Language at Your Fingertips.","name":"Webhook Test","last_tested_version":"1.0.6","endpoint_name":"webhook-test","is_component":false} \ No newline at end of file diff --git a/tests/test_webhook.py b/tests/test_webhook.py index edba88168..ad1286ac3 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -1,6 +1,13 @@ import tempfile from pathlib import Path +import pytest + + +@pytest.fixture(autouse=True) +def check_openai_api_key_in_environment_variables(): + pass + def test_webhook_endpoint(client, added_webhook_test): # The test is as follows: @@ -28,6 +35,18 @@ def test_webhook_endpoint(client, added_webhook_test): assert not file_path.exists() +def test_webhook_flow_on_run_endpoint(client, added_webhook_test, created_api_key): + endpoint_name = added_webhook_test["endpoint_name"] + endpoint = f"api/v1/run/{endpoint_name}?stream=false" + # Just test that "Random Payload" returns 202 + # returns 202 + payload = { + "output_type": "any", + } + response = client.post(endpoint, headers={"x-api-key": created_api_key.api_key}, json=payload) + assert response.status_code == 200, response.json() + + def test_webhook_with_random_payload(client, added_webhook_test): endpoint_name = added_webhook_test["endpoint_name"] endpoint = f"api/v1/webhook/{endpoint_name}"