1389 lines
81 KiB
JSON
1389 lines
81 KiB
JSON
{
|
|
"data": {
|
|
"edges": [
|
|
{
|
|
"animated": false,
|
|
"className": "",
|
|
"data": {
|
|
"sourceHandle": {
|
|
"dataType": "ChatInput",
|
|
"id": "ChatInput-entry",
|
|
"name": "message",
|
|
"output_types": [
|
|
"Message"
|
|
]
|
|
},
|
|
"targetHandle": {
|
|
"fieldName": "payload",
|
|
"id": "CustomComponent-rule",
|
|
"inputTypes": [
|
|
"Message"
|
|
],
|
|
"type": "str"
|
|
}
|
|
},
|
|
"id": "xy-edge__{œdataTypeœ: œChatInputœ, œidœ: œChatInput-entryœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}-{œfieldNameœ: œpayloadœ, œidœ: œCustomComponent-ruleœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}",
|
|
"selected": false,
|
|
"source": "ChatInput-entry",
|
|
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-entryœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
|
|
"target": "CustomComponent-rule",
|
|
"targetHandle": "{œfieldNameœ: œpayloadœ, œidœ: œCustomComponent-ruleœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
|
|
},
|
|
{
|
|
"animated": false,
|
|
"className": "",
|
|
"data": {
|
|
"sourceHandle": {
|
|
"dataType": "CustomComponent",
|
|
"id": "CustomComponent-rule",
|
|
"name": "verdict",
|
|
"output_types": [
|
|
"Message"
|
|
]
|
|
},
|
|
"targetHandle": {
|
|
"fieldName": "input_text",
|
|
"id": "ConditionalRouter-router",
|
|
"inputTypes": [
|
|
"Message"
|
|
],
|
|
"type": "str"
|
|
}
|
|
},
|
|
"id": "xy-edge__{œdataTypeœ: œCustomComponentœ, œidœ: œCustomComponent-ruleœ, œnameœ: œverdictœ, œoutput_typesœ: [œMessageœ]}-{œfieldNameœ: œinput_textœ, œidœ: œConditionalRouter-routerœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}",
|
|
"selected": false,
|
|
"source": "CustomComponent-rule",
|
|
"sourceHandle": "{œdataTypeœ: œCustomComponentœ, œidœ: œCustomComponent-ruleœ, œnameœ: œverdictœ, œoutput_typesœ: [œMessageœ]}",
|
|
"target": "ConditionalRouter-router",
|
|
"targetHandle": "{œfieldNameœ: œinput_textœ, œidœ: œConditionalRouter-routerœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
|
|
},
|
|
{
|
|
"animated": false,
|
|
"className": "",
|
|
"data": {
|
|
"sourceHandle": {
|
|
"dataType": "ConditionalRouter",
|
|
"id": "ConditionalRouter-router",
|
|
"name": "false_result",
|
|
"output_types": [
|
|
"Message"
|
|
]
|
|
},
|
|
"targetHandle": {
|
|
"fieldName": "input_value",
|
|
"id": "ChatOutput-ok",
|
|
"inputTypes": [
|
|
"Data",
|
|
"JSON",
|
|
"DataFrame",
|
|
"Table",
|
|
"Message"
|
|
],
|
|
"type": "other"
|
|
}
|
|
},
|
|
"id": "xy-edge__{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-routerœ, œnameœ: œfalse_resultœ, œoutput_typesœ: [œMessageœ]}-{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-okœ, œinputTypesœ: [œDataœ, œJSONœ, œDataFrameœ, œTableœ, œMessageœ], œtypeœ: œotherœ}",
|
|
"selected": false,
|
|
"source": "ConditionalRouter-router",
|
|
"sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-routerœ, œnameœ: œfalse_resultœ, œoutput_typesœ: [œMessageœ]}",
|
|
"target": "ChatOutput-ok",
|
|
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-okœ, œinputTypesœ: [œDataœ, œJSONœ, œDataFrameœ, œTableœ, œMessageœ], œtypeœ: œotherœ}"
|
|
},
|
|
{
|
|
"animated": false,
|
|
"className": "",
|
|
"data": {
|
|
"sourceHandle": {
|
|
"dataType": "ConditionalRouter",
|
|
"id": "ConditionalRouter-router",
|
|
"name": "true_result",
|
|
"output_types": [
|
|
"Message"
|
|
]
|
|
},
|
|
"targetHandle": {
|
|
"fieldName": "input_value",
|
|
"id": "ChatOutput-sonder",
|
|
"inputTypes": [
|
|
"Data",
|
|
"JSON",
|
|
"DataFrame",
|
|
"Table",
|
|
"Message"
|
|
],
|
|
"type": "other"
|
|
}
|
|
},
|
|
"id": "xy-edge__{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-routerœ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}-{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-sonderœ, œinputTypesœ: [œDataœ, œJSONœ, œDataFrameœ, œTableœ, œMessageœ], œtypeœ: œotherœ}",
|
|
"selected": false,
|
|
"source": "ConditionalRouter-router",
|
|
"sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-routerœ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}",
|
|
"target": "ChatOutput-sonder",
|
|
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-sonderœ, œinputTypesœ: [œDataœ, œJSONœ, œDataFrameœ, œTableœ, œMessageœ], œtypeœ: œotherœ}"
|
|
}
|
|
],
|
|
"nodes": [
|
|
{
|
|
"data": {
|
|
"id": "ChatInput-entry",
|
|
"node": {
|
|
"base_classes": [
|
|
"Message"
|
|
],
|
|
"beta": false,
|
|
"conditional_paths": [],
|
|
"custom_fields": {},
|
|
"description": "Get chat inputs from the Playground.",
|
|
"display_name": "Chat Input",
|
|
"documentation": "https://docs.langflow.org/chat-input-and-output",
|
|
"edited": false,
|
|
"field_order": [],
|
|
"frozen": false,
|
|
"icon": "MessagesSquare",
|
|
"legacy": false,
|
|
"metadata": {},
|
|
"minimized": true,
|
|
"output_types": [],
|
|
"outputs": [
|
|
{
|
|
"allows_loop": false,
|
|
"cache": true,
|
|
"display_name": "Chat Message",
|
|
"group_outputs": false,
|
|
"method": "message_response",
|
|
"name": "message",
|
|
"selected": "Message",
|
|
"tool_mode": true,
|
|
"types": [
|
|
"Message"
|
|
],
|
|
"value": "__UNDEFINED__"
|
|
}
|
|
],
|
|
"pinned": false,
|
|
"template": {
|
|
"_type": "Component",
|
|
"code": {
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"dynamic": true,
|
|
"fileTypes": [],
|
|
"file_path": "",
|
|
"info": "",
|
|
"list": false,
|
|
"load_from_db": false,
|
|
"multiline": true,
|
|
"name": "code",
|
|
"password": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"type": "code",
|
|
"value": "from lfx.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.inputs.inputs import BoolInput\nfrom lfx.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom lfx.schema.image import get_file_paths\nfrom lfx.schema.message import Message\nfrom lfx.services.deps import get_settings_service\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\nfrom lfx.utils.file_path_security import (\n component_file_access_scopes,\n enforce_local_file_access,\n is_local_file_access_restricted,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_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=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n 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 temp_file=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Chat Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n # Ensure files is a list and filter out empty/None values\n files = self.files if self.files else []\n if files and not isinstance(files, list):\n files = [files]\n # Filter out None/empty values\n files = [f for f in files if f is not None and f != \"\"]\n\n # Build endpoints inject caller-controlled file references directly into\n # ChatInput. Resolve and validate local references before the Message can\n # later read them while constructing model attachment content.\n if files:\n settings_service = get_settings_service()\n storage_type = getattr(getattr(settings_service, \"settings\", None), \"storage_type\", \"local\")\n # The storage factory falls back to local storage for unsupported\n # values, so only the explicitly configured S3 backend may bypass\n # filesystem containment.\n if str(storage_type).lower() != \"s3\" and is_local_file_access_restricted():\n scope_ids = list(component_file_access_scopes(self))\n # Public executions replace graph.flow_id with a per-visitor\n # virtual ID. Only the server-populated source provenance may\n # restore the already-validated public attachment namespace.\n source_flow_id = getattr(self.graph, \"source_flow_id\", None)\n if source_flow_id:\n scope_ids.append(source_flow_id)\n for file_path in get_file_paths(files):\n enforce_local_file_access(file_path, scope_ids=scope_ids)\n\n session_id = self.session_id or self.graph.session_id or \"\"\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=session_id,\n context_id=self.context_id,\n files=files,\n )\n if session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
|
|
},
|
|
"context_id": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Context ID",
|
|
"dynamic": false,
|
|
"info": "The context ID of the chat. Adds an extra layer to the local memory.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "context_id",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"files": {
|
|
"_input_type": "FileInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Files",
|
|
"dynamic": false,
|
|
"fileTypes": [
|
|
"csv",
|
|
"json",
|
|
"pdf",
|
|
"txt",
|
|
"md",
|
|
"mdx",
|
|
"yaml",
|
|
"yml",
|
|
"xml",
|
|
"html",
|
|
"htm",
|
|
"docx",
|
|
"py",
|
|
"sh",
|
|
"sql",
|
|
"js",
|
|
"ts",
|
|
"tsx",
|
|
"jpg",
|
|
"jpeg",
|
|
"png",
|
|
"bmp",
|
|
"image"
|
|
],
|
|
"file_path": "",
|
|
"info": "Files to be sent with the message.",
|
|
"list": true,
|
|
"list_add_label": "Add More",
|
|
"name": "files",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"temp_file": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "file",
|
|
"value": ""
|
|
},
|
|
"input_value": {
|
|
"_input_type": "MultilineInput",
|
|
"advanced": false,
|
|
"ai_enabled": false,
|
|
"api_editable": false,
|
|
"copy_field": false,
|
|
"display_name": "Input Text",
|
|
"dynamic": false,
|
|
"info": "Message to be passed as input.",
|
|
"input_types": [],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"multiline": true,
|
|
"name": "input_value",
|
|
"override_skip": false,
|
|
"password": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"sender": {
|
|
"_input_type": "DropdownInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"combobox": false,
|
|
"dialog_inputs": {},
|
|
"display_name": "Sender Type",
|
|
"dynamic": false,
|
|
"external_options": {},
|
|
"info": "Type of sender.",
|
|
"name": "sender",
|
|
"options": [
|
|
"Machine",
|
|
"User"
|
|
],
|
|
"options_metadata": [],
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"toggle": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "str",
|
|
"value": "User"
|
|
},
|
|
"sender_name": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Sender Name",
|
|
"dynamic": false,
|
|
"info": "Name of the sender.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "sender_name",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "User"
|
|
},
|
|
"session_id": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Session ID",
|
|
"dynamic": false,
|
|
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "session_id",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"should_store_message": {
|
|
"_input_type": "BoolInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Store Messages",
|
|
"dynamic": false,
|
|
"info": "Store the message in the history.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "should_store_message",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "bool",
|
|
"value": true
|
|
}
|
|
},
|
|
"tool_mode": false
|
|
},
|
|
"showNode": true,
|
|
"type": "ChatInput"
|
|
},
|
|
"id": "ChatInput-entry",
|
|
"measured": {
|
|
"height": 200,
|
|
"width": 320
|
|
},
|
|
"position": {
|
|
"x": 0,
|
|
"y": 200
|
|
},
|
|
"type": "genericNode"
|
|
},
|
|
{
|
|
"data": {
|
|
"id": "CustomComponent-rule",
|
|
"node": {
|
|
"base_classes": [
|
|
"Message"
|
|
],
|
|
"beta": false,
|
|
"conditional_paths": [],
|
|
"custom_fields": {},
|
|
"description": "Bewertet Betrag gegen Schwelle aus JSON-Eingabe.",
|
|
"display_name": "Rule Check",
|
|
"documentation": "",
|
|
"edited": false,
|
|
"field_order": [
|
|
"payload"
|
|
],
|
|
"frozen": false,
|
|
"legacy": false,
|
|
"metadata": {
|
|
"code_hash": "0b9f8a009fcf",
|
|
"dependencies": {
|
|
"dependencies": [
|
|
{
|
|
"name": "lfx",
|
|
"version": "1.12.0"
|
|
}
|
|
],
|
|
"total_dependencies": 1
|
|
},
|
|
"module": "custom_components.rule_check"
|
|
},
|
|
"minimized": false,
|
|
"output_types": [],
|
|
"outputs": [
|
|
{
|
|
"allows_loop": false,
|
|
"cache": true,
|
|
"display_name": "Verdict",
|
|
"group_outputs": false,
|
|
"method": "check",
|
|
"name": "verdict",
|
|
"selected": "Message",
|
|
"tool_mode": true,
|
|
"types": [
|
|
"Message"
|
|
],
|
|
"value": "__UNDEFINED__"
|
|
}
|
|
],
|
|
"pinned": false,
|
|
"template": {
|
|
"_type": "Component",
|
|
"code": {
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"dynamic": true,
|
|
"fileTypes": [],
|
|
"file_path": "",
|
|
"info": "",
|
|
"list": false,
|
|
"load_from_db": false,
|
|
"multiline": true,
|
|
"name": "code",
|
|
"password": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"type": "code",
|
|
"value": "from lfx.custom import Component\nfrom lfx.io import MessageTextInput, Output\nfrom lfx.schema.message import Message\nimport json\n\n\nclass RuleCheckComponent(Component):\n display_name = \"Rule Check\"\n description = \"Bewertet Betrag gegen Schwelle aus JSON-Eingabe.\"\n\n inputs = [\n MessageTextInput(name=\"payload\", display_name=\"Payload\"),\n ]\n outputs = [\n Output(display_name=\"Verdict\", name=\"verdict\", method=\"check\"),\n ]\n\n def _text(self, value) -> str:\n if value is None:\n return \"\"\n if hasattr(value, \"text\"):\n return value.text or \"\"\n return str(value)\n\n def check(self) -> Message:\n try:\n data = json.loads(self._text(self.payload) or \"{}\")\n except Exception:\n data = {}\n if not isinstance(data, dict):\n data = {}\n # Die API ruft Flows immer mit Huelle auf\n # {\"payload\": {...}, \"run_id\": ..., ...}; rohe Payloads gehen auch.\n if isinstance(data.get(\"payload\"), dict):\n data = data[\"payload\"]\n try:\n betrag = float(data.get(\"betrag\", 0) or 0)\n except (TypeError, ValueError):\n betrag = 0.0\n try:\n schwelle = float(data.get(\"schwelle\", 200000) or 200000)\n except (TypeError, ValueError):\n schwelle = 200000.0\n return Message(text=\"PRUEFUNG\" if betrag > schwelle else \"OK\")\n"
|
|
},
|
|
"payload": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": false,
|
|
"api_editable": false,
|
|
"display_name": "Payload",
|
|
"dynamic": false,
|
|
"info": "",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "payload",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
}
|
|
},
|
|
"tool_mode": false
|
|
},
|
|
"showNode": true,
|
|
"type": "CustomComponent"
|
|
},
|
|
"id": "CustomComponent-rule",
|
|
"measured": {
|
|
"height": 200,
|
|
"width": 320
|
|
},
|
|
"position": {
|
|
"x": 400,
|
|
"y": 200
|
|
},
|
|
"type": "genericNode"
|
|
},
|
|
{
|
|
"data": {
|
|
"id": "ConditionalRouter-router",
|
|
"node": {
|
|
"base_classes": [
|
|
"Message"
|
|
],
|
|
"beta": false,
|
|
"conditional_paths": [],
|
|
"custom_fields": {},
|
|
"description": "Routes an input message to a corresponding output based on text comparison.",
|
|
"display_name": "If-Else",
|
|
"documentation": "https://docs.langflow.org/if-else",
|
|
"edited": false,
|
|
"field_order": [],
|
|
"frozen": false,
|
|
"icon": "split",
|
|
"legacy": false,
|
|
"metadata": {},
|
|
"minimized": false,
|
|
"output_types": [],
|
|
"outputs": [
|
|
{
|
|
"allows_loop": false,
|
|
"cache": true,
|
|
"display_name": "True",
|
|
"group_outputs": true,
|
|
"method": "true_response",
|
|
"name": "true_result",
|
|
"selected": "Message",
|
|
"tool_mode": true,
|
|
"types": [
|
|
"Message"
|
|
],
|
|
"value": "__UNDEFINED__"
|
|
},
|
|
{
|
|
"allows_loop": false,
|
|
"cache": true,
|
|
"display_name": "False",
|
|
"group_outputs": true,
|
|
"method": "false_response",
|
|
"name": "false_result",
|
|
"selected": "Message",
|
|
"tool_mode": true,
|
|
"types": [
|
|
"Message"
|
|
],
|
|
"value": "__UNDEFINED__"
|
|
}
|
|
],
|
|
"pinned": false,
|
|
"template": {
|
|
"_type": "Component",
|
|
"case_sensitive": {
|
|
"_input_type": "BoolInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Case Sensitive",
|
|
"dynamic": false,
|
|
"info": "If true, the comparison will be case sensitive.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "case_sensitive",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "bool",
|
|
"value": true
|
|
},
|
|
"code": {
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"dynamic": true,
|
|
"fileTypes": [],
|
|
"file_path": "",
|
|
"info": "",
|
|
"list": false,
|
|
"load_from_db": false,
|
|
"multiline": true,
|
|
"name": "code",
|
|
"password": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"type": "code",
|
|
"value": "import re\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DropdownInput, IntInput, MessageInput, MessageTextInput, Output\nfrom lfx.schema.message import Message\n\n\nclass ConditionalRouterComponent(Component):\n display_name = \"If-Else\"\n description = \"Routes an input message to a corresponding output based on text comparison.\"\n documentation: str = \"https://docs.langflow.org/if-else\"\n icon = \"split\"\n name = \"ConditionalRouter\"\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self.__iteration_updated = False\n\n inputs = [\n MessageTextInput(\n name=\"input_text\",\n display_name=\"Text Input\",\n info=\"The primary text input for the operation.\",\n required=True,\n ),\n DropdownInput(\n name=\"operator\",\n display_name=\"Operator\",\n options=[\n \"equals\",\n \"not equals\",\n \"contains\",\n \"starts with\",\n \"ends with\",\n \"regex\",\n \"less than\",\n \"less than or equal\",\n \"greater than\",\n \"greater than or equal\",\n ],\n info=\"The operator to apply for comparing the texts.\",\n value=\"equals\",\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"match_text\",\n display_name=\"Match Text\",\n info=\"The text input to compare against.\",\n required=True,\n ),\n BoolInput(\n name=\"case_sensitive\",\n display_name=\"Case Sensitive\",\n info=\"If true, the comparison will be case sensitive.\",\n value=True,\n advanced=True,\n ),\n MessageInput(\n name=\"true_case_message\",\n display_name=\"Case True\",\n info=\"The message to pass if the condition is True.\",\n advanced=True,\n ),\n MessageInput(\n name=\"false_case_message\",\n display_name=\"Case False\",\n info=\"The message to pass if the condition is False.\",\n advanced=True,\n ),\n IntInput(\n name=\"max_iterations\",\n display_name=\"Max Iterations\",\n info=\"The maximum number of iterations for the conditional router.\",\n value=10,\n advanced=True,\n ),\n DropdownInput(\n name=\"default_route\",\n display_name=\"Default Route\",\n options=[\"true_result\", \"false_result\"],\n info=\"The default route to take when max iterations are reached.\",\n value=\"false_result\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"True\", name=\"true_result\", method=\"true_response\", group_outputs=True),\n Output(display_name=\"False\", name=\"false_result\", method=\"false_response\", group_outputs=True),\n ]\n\n def _pre_run_setup(self):\n self.__iteration_updated = False\n\n def evaluate_condition(self, input_text: str, match_text: str, operator: str, *, case_sensitive: bool) -> bool:\n if not case_sensitive and operator != \"regex\":\n input_text = input_text.lower()\n match_text = match_text.lower()\n\n if operator == \"equals\":\n return input_text == match_text\n if operator == \"not equals\":\n return input_text != match_text\n if operator == \"contains\":\n return match_text in input_text\n if operator == \"starts with\":\n return input_text.startswith(match_text)\n if operator == \"ends with\":\n return input_text.endswith(match_text)\n if operator == \"regex\":\n try:\n return bool(re.match(match_text, input_text))\n except re.error:\n return False # Return False if the regex is invalid\n if operator in [\"less than\", \"less than or equal\", \"greater than\", \"greater than or equal\"]:\n try:\n input_num = float(input_text)\n match_num = float(match_text)\n if operator == \"less than\":\n return input_num < match_num\n if operator == \"less than or equal\":\n return input_num <= match_num\n if operator == \"greater than\":\n return input_num > match_num\n if operator == \"greater than or equal\":\n return input_num >= match_num\n except ValueError:\n return False # Invalid number format for comparison\n return False\n\n def iterate_and_stop_once(self, route_to_stop: str):\n \"\"\"Handles cycle iteration counting and branch exclusion.\n\n Uses two complementary mechanisms:\n 1. stop() - ACTIVE/INACTIVE state for cycle management (gets reset each iteration)\n 2. exclude_branch_conditionally() - Persistent exclusion for conditional routing\n\n When max_iterations is reached, breaks the cycle by allowing the default_route to execute.\n \"\"\"\n if not self.__iteration_updated:\n self.update_ctx({f\"{self._id}_iteration\": self.ctx.get(f\"{self._id}_iteration\", 0) + 1})\n self.__iteration_updated = True\n current_iteration = self.ctx.get(f\"{self._id}_iteration\", 0)\n\n # Check if max iterations reached and we're trying to stop the default route\n if current_iteration >= self.max_iterations and route_to_stop == self.default_route:\n # Clear ALL conditional exclusions to allow default route to execute\n if self._id in self.graph.conditional_exclusion_sources:\n previous_exclusions = self.graph.conditional_exclusion_sources[self._id]\n self.graph.conditionally_excluded_vertices -= previous_exclusions\n del self.graph.conditional_exclusion_sources[self._id]\n\n # Switch which route to stop - stop the NON-default route to break the cycle\n route_to_stop = \"true_result\" if route_to_stop == \"false_result\" else \"false_result\"\n\n # Call stop to break the cycle\n self.stop(route_to_stop)\n # Don't apply conditional exclusion when breaking cycle\n return\n\n # Normal case: Use BOTH mechanisms\n # 1. stop() for cycle management (marks INACTIVE, updates run manager, gets reset)\n self.stop(route_to_stop)\n\n # 2. Conditional exclusion for persistent routing (doesn't get reset except by this router)\n self.graph.exclude_branch_conditionally(self._id, output_name=route_to_stop)\n\n def _resolve_case_message(self, case_message: Message | str | None) -> Message:\n \"\"\"Return the override case message, falling back to the input text when it is blank.\n\n ``Case True`` and ``Case False`` are optional overrides. When left blank the\n ``MessageInput`` resolves to an empty ``Message``, so the original ``Text Input``\n is routed through instead of an empty message.\n\n A ``Message`` is only treated as blank when it carries no payload at all. An\n override with empty text but meaningful content (files or content blocks) is\n preserved as-is so that payload is not dropped.\n\n Blankness is judged by payload only (``text``, ``files``, ``content_blocks``).\n A ``Message`` carrying just metadata (``sender``, ``properties``, ``error`` ...)\n and no payload is intentionally treated as blank: every empty ``Message`` also\n defaults ``timestamp`` / ``category`` / ``properties``, so keying on metadata\n would defeat the blank-field fallback. With no payload there is nothing to\n route, so the ``Text Input`` is used.\n\n ``case_message`` is typed defensively as ``Message | str | None``. ``MessageInput``\n wraps user input into a ``Message`` on the validated graph path, but a raw ``str``\n can still arrive via direct/programmatic assignment (``.set(...)`` stores the raw\n value), so the ``str`` branch is retained rather than dropping such an override.\n \"\"\"\n if isinstance(case_message, Message):\n if case_message.text or case_message.files or case_message.content_blocks:\n return case_message\n return Message(text=self.input_text)\n if isinstance(case_message, str) and case_message:\n return Message(text=case_message)\n return Message(text=self.input_text)\n\n def true_response(self) -> Message:\n result = self.evaluate_condition(\n self.input_text, self.match_text, self.operator, case_sensitive=self.case_sensitive\n )\n\n # Check if we should force output due to max_iterations on default route\n current_iteration = self.ctx.get(f\"{self._id}_iteration\", 0)\n force_output = current_iteration >= self.max_iterations and self.default_route == \"true_result\"\n\n if result or force_output:\n true_message = self._resolve_case_message(self.true_case_message)\n self.status = true_message\n if not force_output: # Only stop the other branch if not forcing due to max iterations\n self.iterate_and_stop_once(\"false_result\")\n return true_message\n self.iterate_and_stop_once(\"true_result\")\n return Message(text=\"\")\n\n def false_response(self) -> Message:\n result = self.evaluate_condition(\n self.input_text, self.match_text, self.operator, case_sensitive=self.case_sensitive\n )\n\n if not result:\n false_message = self._resolve_case_message(self.false_case_message)\n self.status = false_message\n self.iterate_and_stop_once(\"true_result\")\n return false_message\n\n self.iterate_and_stop_once(\"false_result\")\n return Message(text=\"\")\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n if field_name == \"operator\":\n if field_value == \"regex\":\n build_config.pop(\"case_sensitive\", None)\n elif \"case_sensitive\" not in build_config:\n case_sensitive_input = next(\n (input_field for input_field in self.inputs if input_field.name == \"case_sensitive\"), None\n )\n if case_sensitive_input:\n build_config[\"case_sensitive\"] = case_sensitive_input.to_dict()\n return build_config\n"
|
|
},
|
|
"default_route": {
|
|
"_input_type": "DropdownInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"combobox": false,
|
|
"dialog_inputs": {},
|
|
"display_name": "Default Route",
|
|
"dynamic": false,
|
|
"external_options": {},
|
|
"info": "The default route to take when max iterations are reached.",
|
|
"name": "default_route",
|
|
"options": [
|
|
"true_result",
|
|
"false_result"
|
|
],
|
|
"options_metadata": [],
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"toggle": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "str",
|
|
"value": "false_result"
|
|
},
|
|
"false_case_message": {
|
|
"_input_type": "MessageInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Case False",
|
|
"dynamic": false,
|
|
"info": "The message to pass if the condition is False.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "false_case_message",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "Standardfall: automatisch freigegeben"
|
|
},
|
|
"input_text": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": false,
|
|
"api_editable": false,
|
|
"display_name": "Text Input",
|
|
"dynamic": false,
|
|
"info": "The primary text input for the operation.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "input_text",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"match_text": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": false,
|
|
"api_editable": false,
|
|
"display_name": "Match Text",
|
|
"dynamic": false,
|
|
"info": "The text input to compare against.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "match_text",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "PRUEFUNG"
|
|
},
|
|
"max_iterations": {
|
|
"_input_type": "IntInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Max Iterations",
|
|
"dynamic": false,
|
|
"info": "The maximum number of iterations for the conditional router.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "max_iterations",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "int",
|
|
"value": 10
|
|
},
|
|
"operator": {
|
|
"_input_type": "DropdownInput",
|
|
"advanced": false,
|
|
"api_editable": false,
|
|
"combobox": false,
|
|
"dialog_inputs": {},
|
|
"display_name": "Operator",
|
|
"dynamic": false,
|
|
"external_options": {},
|
|
"info": "The operator to apply for comparing the texts.",
|
|
"name": "operator",
|
|
"options": [
|
|
"equals",
|
|
"not equals",
|
|
"contains",
|
|
"starts with",
|
|
"ends with",
|
|
"regex",
|
|
"less than",
|
|
"less than or equal",
|
|
"greater than",
|
|
"greater than or equal"
|
|
],
|
|
"options_metadata": [],
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"real_time_refresh": true,
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"toggle": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "str",
|
|
"value": "equals"
|
|
},
|
|
"true_case_message": {
|
|
"_input_type": "MessageInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Case True",
|
|
"dynamic": false,
|
|
"info": "The message to pass if the condition is True.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "true_case_message",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "Sonderfall: menschliche Pruefung erforderlich"
|
|
}
|
|
},
|
|
"tool_mode": false
|
|
},
|
|
"showNode": true,
|
|
"type": "ConditionalRouter"
|
|
},
|
|
"id": "ConditionalRouter-router",
|
|
"measured": {
|
|
"height": 200,
|
|
"width": 320
|
|
},
|
|
"position": {
|
|
"x": 800,
|
|
"y": 200
|
|
},
|
|
"type": "genericNode"
|
|
},
|
|
{
|
|
"data": {
|
|
"id": "ChatOutput-ok",
|
|
"node": {
|
|
"base_classes": [
|
|
"Message"
|
|
],
|
|
"beta": false,
|
|
"conditional_paths": [],
|
|
"custom_fields": {},
|
|
"description": "Display a chat message in the Playground.",
|
|
"display_name": "Chat Output",
|
|
"documentation": "https://docs.langflow.org/chat-input-and-output",
|
|
"edited": false,
|
|
"field_order": [],
|
|
"frozen": false,
|
|
"icon": "MessagesSquare",
|
|
"legacy": false,
|
|
"metadata": {},
|
|
"minimized": true,
|
|
"output_types": [],
|
|
"outputs": [
|
|
{
|
|
"allows_loop": false,
|
|
"cache": true,
|
|
"display_name": "Output Message",
|
|
"group_outputs": false,
|
|
"method": "message_response",
|
|
"name": "message",
|
|
"selected": "Message",
|
|
"tool_mode": true,
|
|
"types": [
|
|
"Message"
|
|
],
|
|
"value": "__UNDEFINED__"
|
|
}
|
|
],
|
|
"pinned": false,
|
|
"template": {
|
|
"_type": "Component",
|
|
"clean_data": {
|
|
"_input_type": "BoolInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Basic Clean Data",
|
|
"dynamic": false,
|
|
"info": "Whether to clean data before converting to string.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "clean_data",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "bool",
|
|
"value": true
|
|
},
|
|
"code": {
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"dynamic": true,
|
|
"fileTypes": [],
|
|
"file_path": "",
|
|
"info": "",
|
|
"list": false,
|
|
"load_from_db": false,
|
|
"multiline": true,
|
|
"name": "code",
|
|
"password": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"type": "code",
|
|
"value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"JSON\", \"DataFrame\", \"Table\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n 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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n # Set accumulated token usage from all upstream LLM vertices.\n # This must happen AFTER send_message() because streaming captures\n # usage from chunks and would overwrite accumulated totals.\n if hasattr(self, \"_vertex\") and self._vertex is not None:\n accumulated_usage = self._vertex._accumulate_upstream_token_usage() # noqa: SLF001\n if accumulated_usage:\n message.properties.usage = accumulated_usage\n if self.should_store_message and message.get_id():\n message = await self._update_stored_message(message)\n await self._send_message_event(message, id_=message.get_id())\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n"
|
|
},
|
|
"context_id": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Context ID",
|
|
"dynamic": false,
|
|
"info": "The context ID of the chat. Adds an extra layer to the local memory.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "context_id",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"data_template": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Data Template",
|
|
"dynamic": false,
|
|
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "data_template",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "{text}"
|
|
},
|
|
"input_value": {
|
|
"_input_type": "HandleInput",
|
|
"advanced": false,
|
|
"api_editable": false,
|
|
"display_name": "Inputs",
|
|
"dynamic": false,
|
|
"info": "Message to be passed as output.",
|
|
"input_types": [
|
|
"Data",
|
|
"JSON",
|
|
"DataFrame",
|
|
"Table",
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "input_value",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "other",
|
|
"value": ""
|
|
},
|
|
"sender": {
|
|
"_input_type": "DropdownInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"combobox": false,
|
|
"dialog_inputs": {},
|
|
"display_name": "Sender Type",
|
|
"dynamic": false,
|
|
"external_options": {},
|
|
"info": "Type of sender.",
|
|
"name": "sender",
|
|
"options": [
|
|
"Machine",
|
|
"User"
|
|
],
|
|
"options_metadata": [],
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"toggle": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "str",
|
|
"value": "Machine"
|
|
},
|
|
"sender_name": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Sender Name",
|
|
"dynamic": false,
|
|
"info": "Name of the sender.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "sender_name",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "AI"
|
|
},
|
|
"session_id": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Session ID",
|
|
"dynamic": false,
|
|
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "session_id",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"should_store_message": {
|
|
"_input_type": "BoolInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Store Messages",
|
|
"dynamic": false,
|
|
"info": "Store the message in the history.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "should_store_message",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "bool",
|
|
"value": true
|
|
}
|
|
},
|
|
"tool_mode": false
|
|
},
|
|
"showNode": true,
|
|
"type": "ChatOutput"
|
|
},
|
|
"id": "ChatOutput-ok",
|
|
"measured": {
|
|
"height": 200,
|
|
"width": 320
|
|
},
|
|
"position": {
|
|
"x": 1200,
|
|
"y": 60
|
|
},
|
|
"type": "genericNode"
|
|
},
|
|
{
|
|
"data": {
|
|
"id": "ChatOutput-sonder",
|
|
"node": {
|
|
"base_classes": [
|
|
"Message"
|
|
],
|
|
"beta": false,
|
|
"conditional_paths": [],
|
|
"custom_fields": {},
|
|
"description": "Display a chat message in the Playground.",
|
|
"display_name": "Chat Output",
|
|
"documentation": "https://docs.langflow.org/chat-input-and-output",
|
|
"edited": false,
|
|
"field_order": [],
|
|
"frozen": false,
|
|
"icon": "MessagesSquare",
|
|
"legacy": false,
|
|
"metadata": {},
|
|
"minimized": true,
|
|
"output_types": [],
|
|
"outputs": [
|
|
{
|
|
"allows_loop": false,
|
|
"cache": true,
|
|
"display_name": "Output Message",
|
|
"group_outputs": false,
|
|
"method": "message_response",
|
|
"name": "message",
|
|
"selected": "Message",
|
|
"tool_mode": true,
|
|
"types": [
|
|
"Message"
|
|
],
|
|
"value": "__UNDEFINED__"
|
|
}
|
|
],
|
|
"pinned": false,
|
|
"template": {
|
|
"_type": "Component",
|
|
"clean_data": {
|
|
"_input_type": "BoolInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Basic Clean Data",
|
|
"dynamic": false,
|
|
"info": "Whether to clean data before converting to string.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "clean_data",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "bool",
|
|
"value": true
|
|
},
|
|
"code": {
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"dynamic": true,
|
|
"fileTypes": [],
|
|
"file_path": "",
|
|
"info": "",
|
|
"list": false,
|
|
"load_from_db": false,
|
|
"multiline": true,
|
|
"name": "code",
|
|
"password": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"type": "code",
|
|
"value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"JSON\", \"DataFrame\", \"Table\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n 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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n # Set accumulated token usage from all upstream LLM vertices.\n # This must happen AFTER send_message() because streaming captures\n # usage from chunks and would overwrite accumulated totals.\n if hasattr(self, \"_vertex\") and self._vertex is not None:\n accumulated_usage = self._vertex._accumulate_upstream_token_usage() # noqa: SLF001\n if accumulated_usage:\n message.properties.usage = accumulated_usage\n if self.should_store_message and message.get_id():\n message = await self._update_stored_message(message)\n await self._send_message_event(message, id_=message.get_id())\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n"
|
|
},
|
|
"context_id": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Context ID",
|
|
"dynamic": false,
|
|
"info": "The context ID of the chat. Adds an extra layer to the local memory.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "context_id",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"data_template": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Data Template",
|
|
"dynamic": false,
|
|
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "data_template",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "{text}"
|
|
},
|
|
"input_value": {
|
|
"_input_type": "HandleInput",
|
|
"advanced": false,
|
|
"api_editable": false,
|
|
"display_name": "Inputs",
|
|
"dynamic": false,
|
|
"info": "Message to be passed as output.",
|
|
"input_types": [
|
|
"Data",
|
|
"JSON",
|
|
"DataFrame",
|
|
"Table",
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "input_value",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": true,
|
|
"show": true,
|
|
"title_case": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "other",
|
|
"value": ""
|
|
},
|
|
"sender": {
|
|
"_input_type": "DropdownInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"combobox": false,
|
|
"dialog_inputs": {},
|
|
"display_name": "Sender Type",
|
|
"dynamic": false,
|
|
"external_options": {},
|
|
"info": "Type of sender.",
|
|
"name": "sender",
|
|
"options": [
|
|
"Machine",
|
|
"User"
|
|
],
|
|
"options_metadata": [],
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"toggle": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "str",
|
|
"value": "Machine"
|
|
},
|
|
"sender_name": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Sender Name",
|
|
"dynamic": false,
|
|
"info": "Name of the sender.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "sender_name",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": "AI"
|
|
},
|
|
"session_id": {
|
|
"_input_type": "MessageTextInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Session ID",
|
|
"dynamic": false,
|
|
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
|
"input_types": [
|
|
"Message"
|
|
],
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"load_from_db": false,
|
|
"name": "session_id",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_input": true,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": false,
|
|
"type": "str",
|
|
"value": ""
|
|
},
|
|
"should_store_message": {
|
|
"_input_type": "BoolInput",
|
|
"advanced": true,
|
|
"api_editable": false,
|
|
"display_name": "Store Messages",
|
|
"dynamic": false,
|
|
"info": "Store the message in the history.",
|
|
"list": false,
|
|
"list_add_label": "Add More",
|
|
"name": "should_store_message",
|
|
"override_skip": false,
|
|
"placeholder": "",
|
|
"required": false,
|
|
"show": true,
|
|
"title_case": false,
|
|
"tool_mode": false,
|
|
"trace_as_metadata": true,
|
|
"track_in_telemetry": true,
|
|
"type": "bool",
|
|
"value": true
|
|
}
|
|
},
|
|
"tool_mode": false
|
|
},
|
|
"showNode": true,
|
|
"type": "ChatOutput"
|
|
},
|
|
"id": "ChatOutput-sonder",
|
|
"measured": {
|
|
"height": 200,
|
|
"width": 320
|
|
},
|
|
"position": {
|
|
"x": 1200,
|
|
"y": 340
|
|
},
|
|
"type": "genericNode"
|
|
}
|
|
],
|
|
"viewport": {
|
|
"x": 0,
|
|
"y": 0,
|
|
"zoom": 1
|
|
}
|
|
},
|
|
"description": "Regelpruefung ohne Seiteneffekt (Spike-Fixture, kein LLM).",
|
|
"endpoint_name": null,
|
|
"id": "5adedb2b-d50b-5c3f-9951-2e76b8b94869",
|
|
"is_component": false,
|
|
"name": "simple_rule",
|
|
"tags": [],
|
|
"webhook": false
|
|
}
|