refactor: migrate from Record to Message (#2113)
* chore: Update launch.json to use debugpy instead of python for debugging * refactor: Update import statements for Record in langflow components * feat: Add image handling functionality to langflow schema * update projects * 📝 (constants.py): Add 'output_types' to NODE_FORMAT_ATTRIBUTES for consistency and completeness ♻️ (setup.py): Refactor imports to improve readability and maintainability ♻️ (setup.py): Update code to remove fields that are not in the latest template for consistency * refactor: Update schema from Record to Message * refactor: Remove print statement in MonitorService * refactor: Remove fields not in the latest template for consistency * refactor: Update code to handle Record objects in utils.py * update projects * 📝 (monitor.py): Add type hint for message_id parameter in update_message function 📝 (parse.py): Rename ParsedContext to ParsedArgs for clarity 📝 (chat.py): Remove unused imports and methods in ChatComponent class 📝 (StoreMessage.py): Change return type of store_message method from list[Record] to list[Message] 📝 (base.py): Change type hint from Dict[str, str | list[str]] to Mapping[str, str | list[str]] in update_raw_params method 📝 (loading.py): Add condition to check if raw is not None before accessing its attributes in instantiate_custom_component function 📝 (memory.py): Change return type of get_messages function from list[Record] to list[Message] 📝 (memory.py): Change parameter type of add_messages function from Message to Message | list[Message] 📝 (image.py): Add type hint for image_prompt_value variable in Message class 🐛 (record.py): fix type hint for image_prompt_value variable to ImagePromptValue to improve code clarity and maintainability * chore: Add orjson options for serialization * chore: Update orjson options for serialization in setup.py * chore: Update input_value options for models This commit updates the input_value options for the models in the `OpenAIModel.py`, `MistralModel.py`, `CohereModel.py`, `VertexAiModel.py`, `ChatLiteLLMModel.py`, `OllamaModel.py`, `HuggingFaceModel.py`, `AnthropicModel.py`, and `AmazonBedrockModel.py` files. The `input_value` now supports the additional input type "Prompt". This change allows for more flexibility in the input data that can be provided to the models. Fixes #<issue_number> * chore: Update edges with latest component versions This commit updates the edges in the project data with the latest component versions. It ensures that the source and target nodes are correctly updated based on their corresponding nodes in the project. The commit also includes escaping of JSON dumps for the source and target handles in the edges. * 📝 (utils.py): Remove unnecessary async keyword from dict_values_to_string function to improve code readability and consistency 🔧 (utils.py): Simplify handling of Message objects by directly accessing the text property instead of calling to_lc_message() method * chore: Refactor PromptComponent to use updated Prompt class and remove unused imports * feat: Add support for image files in Message model This commit modifies the Message model to support image files as attachments. It introduces the `is_image_file` function to check if a file is an image, and the `to_content_dict` method in the Image class to convert the image object to a content dictionary. Additionally, the `get_file_content_dicts` method is added to generate content dictionaries for all files in the message, including images. This enhancement improves the handling of image attachments in the messaging system. Fixes #<issue_number> * update projects and lock * chore: Update LCModelComponent to use Prompt instead of Record * refactor: Update artifact type to include message in utils.py * fix: Add check for input_value to only pass if string * ✨ (switchOutputView/index.tsx): introduce constant RECORD_TYPES to store valid record types for better readability and maintainability 🔧 (switchOutputView/index.tsx): refactor switch cases to use RECORD_TYPES constant for checking valid record types and simplify the logic for handling different types of result messages * feat: Enable loading from database for openai_api_key field in Langflow starter projects This commit updates the Langflow starter projects by enabling the loading of the `openai_api_key` field from the database. Previously, the field was not being loaded from the database, but now it will be loaded and used in the projects. This change improves the functionality and flexibility of the projects. Fixes #<issue_number> * ♻️ (constants.py): remove unnecessary import statement and clean up code formatting in ORJSON_OPTIONS constant definition * refactor: Update MemoryComponent to use messages instead of records This commit updates the MemoryComponent class in the langflow/components/helpers/MemoryComponent.py file to use the term "messages" instead of "records" for better clarity and consistency. It also updates the get_messages method to return a list of Message objects instead of Record objects. This change improves the naming and readability of the code. * refactor: Update Message model to include timestamp conversion function This commit updates the Message model in the langflow/schema/message.py file to include a new function `_timestamp_to_str` that converts the timestamp to a string format. This function is used as a BeforeValidator for the `timestamp` field, ensuring that it is always formatted correctly. This change improves the consistency and reliability of the timestamp handling in the messaging system. * refactor: Update test_data_components.py to improve directory component loading This commit updates the test_data_components.py file to improve the loading of the directory component. It ensures that the directory component can load mdx files from the ../docs/docs/components directory. This change enhances the functionality and reliability of the directory component. Fixes #<issue_number> * refactor: Update .gitattributes to specify working-tree-encoding for .mdx and .json files This commit updates the .gitattributes file to specify the working-tree-encoding for .mdx and .json files. It sets the encoding to UTF-8 for both file types, ensuring consistent handling of character encoding. This change improves the reliability and compatibility of the repository. Fixes #<issue_number> * fix: 🐛 corrects encoding error * refactor: Update toolkits.mdx to improve documentation and fix formatting * refactor: Add dictdiffer library as a dependency This commit adds the dictdiffer library as a dependency in the poetry.lock file. The dictdiffer library is a useful tool for diffing and patching dictionaries. It will enhance the functionality and flexibility of the project.
This commit is contained in:
parent
3ddd42b127
commit
df57570852
98 changed files with 8846 additions and 8116 deletions
5
.gitattributes
vendored
5
.gitattributes
vendored
|
|
@ -11,12 +11,12 @@
|
|||
*.ts text
|
||||
*.tsx text
|
||||
*.md text
|
||||
*.mdx text
|
||||
*.mdx text working-tree-encoding = UTF-8
|
||||
*.yml text
|
||||
*.yaml text
|
||||
*.xml text
|
||||
*.csv text
|
||||
*.json text
|
||||
*.json text working-tree-encoding = UTF-8
|
||||
*.sh text
|
||||
*.Dockerfile text
|
||||
Dockerfile text
|
||||
|
|
@ -32,3 +32,4 @@ Dockerfile text
|
|||
*.mp4 binary
|
||||
*.svg binary
|
||||
*.csv binary
|
||||
|
||||
|
|
|
|||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
|
@ -3,7 +3,7 @@
|
|||
"configurations": [
|
||||
{
|
||||
"name": "Debug Backend",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "uvicorn",
|
||||
"args": [
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
},
|
||||
{
|
||||
"name": "Debug CLI",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "langflow",
|
||||
"args": [
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
{
|
||||
"name": "Python: Remote Attach",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"justMyCode": true,
|
||||
"connect": {
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
},
|
||||
{
|
||||
"name": "Python: Debug Tests",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"purpose": ["debug-test"],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import Admonition from '@theme/Admonition';
|
||||
import Admonition from "@theme/Admonition";
|
||||
|
||||
# Toolkits
|
||||
|
||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||
<p>
|
||||
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
|
||||
</p>
|
||||
</Admonition>
|
||||
<p>
|
||||
We appreciate your understanding as we polish our documentation - it may
|
||||
contain some rough edges. Share your feedback or report issues to help us
|
||||
improve! 🛠️📝
|
||||
</p>
|
||||
</Admonition>
|
||||
|
|
|
|||
94
poetry.lock
generated
94
poetry.lock
generated
|
|
@ -471,17 +471,17 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.34.121"
|
||||
version = "1.34.122"
|
||||
description = "The AWS SDK for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "boto3-1.34.121-py3-none-any.whl", hash = "sha256:4e79e400d6d44b4eee5deda6ac0ecd08a3f5a30c45a0d30712795cdc4459fd79"},
|
||||
{file = "boto3-1.34.121.tar.gz", hash = "sha256:ec89f3e0b0dc959c418df29e14d3748c0b05ab7acf7c0b90c839e9f340a659fa"},
|
||||
{file = "boto3-1.34.122-py3-none-any.whl", hash = "sha256:b2d7400ff84fa547e53b3d9acfa3c95d65d45b5886ba1ede1f7df4768d1cc0b1"},
|
||||
{file = "boto3-1.34.122.tar.gz", hash = "sha256:56840d8ce91654d182f1c113f0791fa2113c3aa43230c50b4481f235348a6037"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.34.121,<1.35.0"
|
||||
botocore = ">=1.34.122,<1.35.0"
|
||||
jmespath = ">=0.7.1,<2.0.0"
|
||||
s3transfer = ">=0.10.0,<0.11.0"
|
||||
|
||||
|
|
@ -490,13 +490,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
|||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.34.121"
|
||||
version = "1.34.122"
|
||||
description = "Low-level, data-driven core of boto 3."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "botocore-1.34.121-py3-none-any.whl", hash = "sha256:25b05c7646a9f240cde1c8f839552a43f27e71e15c42600275dea93e219f7dd9"},
|
||||
{file = "botocore-1.34.121.tar.gz", hash = "sha256:1a8f94b917c47dfd84a0b531ab607dc53570efb0d073d8686600f2d2be985323"},
|
||||
{file = "botocore-1.34.122-py3-none-any.whl", hash = "sha256:6d75df3af831b62f0c7baa109728d987e0a8d34bfadf0476eb32e2f29a079a36"},
|
||||
{file = "botocore-1.34.122.tar.gz", hash = "sha256:9374e16a36f1062c3e27816e8599b53eba99315dfac71cc84fc3aee3f5d3cbe3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -1450,13 +1450,13 @@ tests = ["pytest"]
|
|||
|
||||
[[package]]
|
||||
name = "dataclasses-json"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
description = "Easily serialize dataclasses to and from JSON."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.7"
|
||||
files = [
|
||||
{file = "dataclasses_json-0.6.6-py3-none-any.whl", hash = "sha256:e54c5c87497741ad454070ba0ed411523d46beb5da102e221efb873801b0ba85"},
|
||||
{file = "dataclasses_json-0.6.6.tar.gz", hash = "sha256:0c09827d26fffda27f1be2fed7a7a01a29c5ddcd2eb6393ad5ebf9d77e9deae8"},
|
||||
{file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"},
|
||||
{file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -1590,6 +1590,23 @@ files = [
|
|||
[package.dependencies]
|
||||
packaging = "*"
|
||||
|
||||
[[package]]
|
||||
name = "dictdiffer"
|
||||
version = "0.9.0"
|
||||
description = "Dictdiffer is a library that helps you to diff and patch dictionaries."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"},
|
||||
{file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["Sphinx (>=3)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "tox (>=3.7.0)"]
|
||||
docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"]
|
||||
numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"]
|
||||
tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "tox (>=3.7.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "dill"
|
||||
version = "0.3.7"
|
||||
|
|
@ -2392,8 +2409,8 @@ files = [
|
|||
[package.dependencies]
|
||||
cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
|
||||
greenlet = [
|
||||
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
|
||||
{version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""},
|
||||
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
|
||||
]
|
||||
"zope.event" = "*"
|
||||
"zope.interface" = "*"
|
||||
|
|
@ -2520,12 +2537,12 @@ files = [
|
|||
google-auth = ">=2.14.1,<3.0.dev0"
|
||||
googleapis-common-protos = ">=1.56.2,<2.0.dev0"
|
||||
grpcio = [
|
||||
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
|
||||
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
|
||||
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
|
||||
]
|
||||
grpcio-status = [
|
||||
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
|
||||
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
|
||||
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
|
||||
]
|
||||
proto-plus = ">=1.22.3,<2.0.0dev"
|
||||
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
|
||||
|
|
@ -2594,13 +2611,13 @@ httplib2 = ">=0.19.0"
|
|||
|
||||
[[package]]
|
||||
name = "google-cloud-aiplatform"
|
||||
version = "1.54.0"
|
||||
version = "1.54.1"
|
||||
description = "Vertex AI API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "google-cloud-aiplatform-1.54.0.tar.gz", hash = "sha256:6f5187d35a32951028465804fbb42b478362bf41e2b634ddd22b150299f6e1d8"},
|
||||
{file = "google_cloud_aiplatform-1.54.0-py2.py3-none-any.whl", hash = "sha256:7b3ed849b9fb59a01bd6f44444ccbb7d18495b867a26f913542f6b2d4c3de252"},
|
||||
{file = "google-cloud-aiplatform-1.54.1.tar.gz", hash = "sha256:01c231961cc1a1a3b049ea3ef71fb11e77b2d56d632d020ce09e419b27ff77f2"},
|
||||
{file = "google_cloud_aiplatform-1.54.1-py2.py3-none-any.whl", hash = "sha256:43f70fcd572f15317d769e5a0e04cfb7c0e259ead3fe581d2fba4f203ace5617"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -4291,13 +4308,13 @@ extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "langchainhub"
|
||||
version = "0.1.17"
|
||||
version = "0.1.18"
|
||||
description = "The LangChain Hub API client"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchainhub-0.1.17-py3-none-any.whl", hash = "sha256:4c609b3948252c71670f0d98f73413b515cfd2f6701a7b40ce959203e6133e04"},
|
||||
{file = "langchainhub-0.1.17.tar.gz", hash = "sha256:af7df0cb1cebc7a6e0864e8632ae48ecad39ed96568f699c78657b9d04e50b46"},
|
||||
{file = "langchainhub-0.1.18-py3-none-any.whl", hash = "sha256:11501f15e7f34715ecc8892587daa35c6f2a3005e1f2926c9bcabd31fc2c100c"},
|
||||
{file = "langchainhub-0.1.18.tar.gz", hash = "sha256:f2d0d8bf3abe4ca5e70511d8220bdc9ccea28d5267bcfd0e5ef9c53bd5bd3bad"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -4363,13 +4380,13 @@ url = "src/backend/base"
|
|||
|
||||
[[package]]
|
||||
name = "langfuse"
|
||||
version = "2.35.0"
|
||||
version = "2.35.2"
|
||||
description = "A client library for accessing langfuse"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langfuse-2.35.0-py3-none-any.whl", hash = "sha256:e9df2474a01f8e167b7b13674c554915415b27064e48ad207054475f7fa8f82d"},
|
||||
{file = "langfuse-2.35.0.tar.gz", hash = "sha256:b1d4b478233eefbc8a6fc63ca00ca82f6afecf2b0fdc1835ca65e751cf901577"},
|
||||
{file = "langfuse-2.35.2-py3-none-any.whl", hash = "sha256:d01a23842cab484594f03878aacb9732ef8fd361158eb819c7bf43f758a0954b"},
|
||||
{file = "langfuse-2.35.2.tar.gz", hash = "sha256:32b2e6c5bc71b4efdc430c6b964ab1c1e1ba1e105a4a73912c38b3959dc4502d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -4403,13 +4420,13 @@ requests = ">=2,<3"
|
|||
|
||||
[[package]]
|
||||
name = "litellm"
|
||||
version = "1.40.4"
|
||||
version = "1.40.7"
|
||||
description = "Library to easily interface with LLM API providers"
|
||||
optional = false
|
||||
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
|
||||
files = [
|
||||
{file = "litellm-1.40.4-py3-none-any.whl", hash = "sha256:b3b8e4401f717c3a18595446bfdb80fc6bb74974aac4eae537fb7b3be37fbf9e"},
|
||||
{file = "litellm-1.40.4.tar.gz", hash = "sha256:3edaa1189742afd7c7df2b122f77373d47154a8fb6df6187ff5875e188baa3e1"},
|
||||
{file = "litellm-1.40.7-py3-none-any.whl", hash = "sha256:c98dd8733e632aba16f14bf82e56f7159222097a6d085b242a3140b5d3e7baa4"},
|
||||
{file = "litellm-1.40.7.tar.gz", hash = "sha256:557bb19e8e484d0dfe8e4eaa9ccefc888617852988a46d6e7adc41585a2c0600"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -4451,13 +4468,13 @@ test = ["httpx (>=0.24.1)", "pytest (>=7.4.0)", "scipy (>=1.10)"]
|
|||
|
||||
[[package]]
|
||||
name = "locust"
|
||||
version = "2.28.0"
|
||||
version = "2.29.0"
|
||||
description = "Developer-friendly load testing framework"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "locust-2.28.0-py3-none-any.whl", hash = "sha256:766be879db030c0118e7d9fca712f3538c4e628bdebf59468fa1c6c2fab217d3"},
|
||||
{file = "locust-2.28.0.tar.gz", hash = "sha256:260557eec866f7e34a767b6c916b5b278167562a280480aadb88f43d962fbdeb"},
|
||||
{file = "locust-2.29.0-py3-none-any.whl", hash = "sha256:aa9d94d3604ed9f2aab3248460d91e55d3de980a821dffdf8658b439b049d03f"},
|
||||
{file = "locust-2.29.0.tar.gz", hash = "sha256:649c99ce49d00720a3084c0109547035ad9021222835386599a8b545d31ebe51"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -4471,7 +4488,10 @@ msgpack = ">=1.0.0"
|
|||
psutil = ">=5.9.1"
|
||||
pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
pyzmq = ">=25.0.0"
|
||||
requests = ">=2.26.0"
|
||||
requests = [
|
||||
{version = ">=2.32.2", markers = "python_version > \"3.11\""},
|
||||
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
|
||||
]
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
Werkzeug = ">=2.0.0"
|
||||
|
||||
|
|
@ -5567,13 +5587,13 @@ sympy = "*"
|
|||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.32.0"
|
||||
version = "1.33.0"
|
||||
description = "The official Python library for the openai API"
|
||||
optional = false
|
||||
python-versions = ">=3.7.1"
|
||||
files = [
|
||||
{file = "openai-1.32.0-py3-none-any.whl", hash = "sha256:953d57669f309002044fd2f678aba9f07a43256d74b3b00cd04afb5b185568ea"},
|
||||
{file = "openai-1.32.0.tar.gz", hash = "sha256:a6df15a7ab9344b1bc2bc8d83639f68b7a7e2453c0f5e50c1666547eee86f0bd"},
|
||||
{file = "openai-1.33.0-py3-none-any.whl", hash = "sha256:621163b56570897ab8389d187f686a53d4771fd6ce95d481c0a9611fe8bc4229"},
|
||||
{file = "openai-1.33.0.tar.gz", hash = "sha256:1169211a7b326ecbc821cafb427c29bfd0871f9a3e0947dd9e51acb3b0f1df78"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -5899,9 +5919,9 @@ files = [
|
|||
|
||||
[package.dependencies]
|
||||
numpy = [
|
||||
{version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""},
|
||||
{version = ">=1.22.4,<2", markers = "python_version < \"3.11\""},
|
||||
{version = ">=1.23.2,<2", markers = "python_version == \"3.11\""},
|
||||
{version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""},
|
||||
]
|
||||
python-dateutil = ">=2.8.2"
|
||||
pytz = ">=2020.1"
|
||||
|
|
@ -9025,13 +9045,13 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.1"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"},
|
||||
{file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"},
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -10038,4 +10058,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "2ba268be17a69253c9631ec721ece465a85a22949c2df7c712b7aa12d1a002fa"
|
||||
content-hash = "0ee3f3bef82d57be2ab4ae7b70215ebca67b5bd5223e6a9322ee1837516a3cc6"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ pytest-asyncio = "^0.23.0"
|
|||
pytest-profiling = "^1.7.0"
|
||||
pre-commit = "^3.7.0"
|
||||
vulture = "^2.11"
|
||||
dictdiffer = "^0.9.0"
|
||||
|
||||
[tool.poetry.extras]
|
||||
deploy = ["celery", "redis", "flower"]
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ async def build_vertex(
|
|||
else:
|
||||
graph = cache.get("result")
|
||||
vertex = graph.get_vertex(vertex_id)
|
||||
log_object = None
|
||||
|
||||
try:
|
||||
lock = chat_service._cache_locks[flow_id_str]
|
||||
(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
||||
from langflow.services.deps import get_monitor_service
|
||||
|
|
@ -79,7 +80,7 @@ async def delete_messages(
|
|||
|
||||
@router.post("/messages/{message_id}", response_model=MessageModelResponse)
|
||||
async def update_message(
|
||||
message_id: str,
|
||||
message_id: int,
|
||||
message: MessageModelRequest,
|
||||
monitor_service: MonitorService = Depends(get_monitor_service),
|
||||
):
|
||||
|
|
@ -135,3 +136,4 @@ async def get_transactions(
|
|||
return result
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ from datetime import datetime, timezone
|
|||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing_extensions import TypedDict
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_serializer
|
||||
|
|
@ -15,7 +14,6 @@ from langflow.services.database.models.api_key.model import ApiKeyRead
|
|||
from langflow.services.database.models.base import orjson_dumps
|
||||
from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||
from langflow.services.database.models.user import UserRead
|
||||
from langflow.utils.schemas import ChatOutputResponse
|
||||
|
||||
|
||||
class BuildStatus(Enum):
|
||||
|
|
@ -244,6 +242,7 @@ class VerticesOrderResponse(BaseModel):
|
|||
run_id: UUID
|
||||
vertices_to_run: List[str]
|
||||
|
||||
|
||||
class ResultDataResponse(BaseModel):
|
||||
results: Optional[Any] = Field(default_factory=dict)
|
||||
logs: List[Log | None] = Field(default_factory=list)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from langchain_core.runnables import Runnable
|
|||
from langflow.base.agents.utils import get_agents_list, records_to_messages
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text, Tool
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class LCAgentComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate
|
|||
from langchain_core.tools import BaseTool
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
from .default_prompts import XML_AGENT_PROMPT
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ Constants:
|
|||
- FIELD_FORMAT_ATTRIBUTES: A list of attributes used for formatting fields.
|
||||
"""
|
||||
|
||||
import orjson
|
||||
|
||||
STREAM_INFO_TEXT = "Stream the response from the model. Streaming works only in Chat."
|
||||
|
||||
NODE_FORMAT_ATTRIBUTES = ["beta", "icon", "display_name", "description"]
|
||||
NODE_FORMAT_ATTRIBUTES = ["beta", "icon", "display_name", "description", "output_types"]
|
||||
|
||||
|
||||
FIELD_FORMAT_ATTRIBUTES = [
|
||||
|
|
@ -27,3 +29,5 @@ FIELD_FORMAT_ATTRIBUTES = [
|
|||
"refresh_button_text",
|
||||
"options",
|
||||
]
|
||||
|
||||
ORJSON_OPTIONS = orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS | orjson.OPT_OMIT_MICROSECONDS
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from collections import OrderedDict, namedtuple
|
|||
from http.cookies import SimpleCookie
|
||||
|
||||
ParsedArgs = namedtuple(
|
||||
"ParsedContext",
|
||||
"ParsedArgs",
|
||||
[
|
||||
"command",
|
||||
"url",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import json
|
||||
import unicodedata
|
||||
import xml.etree.ElementTree as ET
|
||||
from concurrent import futures
|
||||
from pathlib import Path
|
||||
from typing import Callable, List, Optional, Text
|
||||
import unicodedata
|
||||
|
||||
import chardet
|
||||
import orjson
|
||||
import yaml
|
||||
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
# Types of files that can be read simply by file.read()
|
||||
# and have 100% to be completely readable
|
||||
|
|
@ -106,7 +107,7 @@ def read_text_file(file_path: str) -> str:
|
|||
result = chardet.detect(raw_data)
|
||||
encoding = result["encoding"]
|
||||
|
||||
if encoding in ["Windows-1254", "MacRoman"]:
|
||||
if encoding in ["Windows-1252", "Windows-1254"]:
|
||||
encoding = "utf-8"
|
||||
|
||||
with open(file_path, "r", encoding=encoding) as f:
|
||||
|
|
@ -139,7 +140,7 @@ def parse_text_file_to_record(file_path: str, silent_errors: bool) -> Optional[R
|
|||
|
||||
# if file is json, yaml, or xml, we can parse it
|
||||
if file_path.endswith(".json"):
|
||||
text = json.loads(text)
|
||||
text = orjson.loads(text)
|
||||
if isinstance(text, dict):
|
||||
text = {k: normalize_text(v) if isinstance(v, str) else v for k, v in text.items()}
|
||||
elif isinstance(text, list):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import List
|
||||
|
||||
from langflow.graph.schema import ResultData, RunOutputs
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
def build_records_from_run_outputs(run_outputs: RunOutputs) -> List[Record]:
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ from typing import Optional, Union
|
|||
|
||||
from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.helpers.record import records_to_text
|
||||
from langflow.memory import store_message
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class ChatComponent(CustomComponent):
|
||||
|
|
@ -52,102 +51,34 @@ class ChatComponent(CustomComponent):
|
|||
|
||||
def store_message(
|
||||
self,
|
||||
message: Union[str, Text, Record],
|
||||
session_id: Optional[str] = None,
|
||||
sender: Optional[str] = None,
|
||||
sender_name: Optional[str] = None,
|
||||
) -> list[Record]:
|
||||
records = store_message(
|
||||
message: Message,
|
||||
) -> list[Message]:
|
||||
messages = store_message(
|
||||
message,
|
||||
session_id=session_id,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
flow_id=self.graph.flow_id,
|
||||
)
|
||||
|
||||
self.status = records
|
||||
return records
|
||||
self.status = messages
|
||||
return messages
|
||||
|
||||
def build_with_record(
|
||||
self,
|
||||
sender: Optional[str] = "User",
|
||||
sender_name: Optional[str] = "User",
|
||||
input_value: Optional[Union[str, Record]] = None,
|
||||
input_value: Optional[Union[str, Record, Message]] = None,
|
||||
files: Optional[list[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
return_record: Optional[bool] = False,
|
||||
record_template: str = "Text: {text}\nData: {data}",
|
||||
) -> Union[Text, Record]:
|
||||
input_value_record: Optional[Record] = None
|
||||
if return_record:
|
||||
if isinstance(input_value, Record):
|
||||
# Update the data of the record
|
||||
input_value.data["sender"] = sender
|
||||
input_value.data["sender_name"] = sender_name
|
||||
input_value.data["session_id"] = session_id
|
||||
input_value.data["files"] = files
|
||||
else:
|
||||
input_value_record = Record(
|
||||
text=input_value,
|
||||
data={
|
||||
"sender": sender,
|
||||
"sender_name": sender_name,
|
||||
"session_id": session_id,
|
||||
"files": files,
|
||||
},
|
||||
)
|
||||
elif isinstance(input_value, Record):
|
||||
input_value = records_to_text(template=record_template, records=input_value)
|
||||
if not input_value:
|
||||
input_value = ""
|
||||
if return_record and input_value_record:
|
||||
result: Union[Text, Record] = input_value_record
|
||||
else:
|
||||
result = input_value
|
||||
self.status = result
|
||||
if session_id and isinstance(result, (Record, str)):
|
||||
self.store_message(result, session_id, sender, sender_name)
|
||||
return result
|
||||
) -> Message:
|
||||
message: Message | None = None
|
||||
|
||||
def build_no_record(
|
||||
self,
|
||||
sender: Optional[str] = "User",
|
||||
sender_name: Optional[str] = "User",
|
||||
input_value: Optional[str] = None,
|
||||
files: Optional[list[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
return_record: Optional[bool] = False,
|
||||
record_template: str = "Text: {text}\nData: {data}",
|
||||
) -> Union[Text, Record]:
|
||||
input_value_record: Optional[Record] = None
|
||||
if files and not return_record:
|
||||
raise ValueError("Files can only be provided when Return Record is enabled.")
|
||||
if return_record:
|
||||
if isinstance(input_value, Record):
|
||||
# Update the data of the record
|
||||
input_value.data["sender"] = sender
|
||||
input_value.data["sender_name"] = sender_name
|
||||
input_value.data["session_id"] = session_id
|
||||
input_value.data["files"] = files
|
||||
else:
|
||||
input_value_record = Record(
|
||||
text=input_value,
|
||||
data={
|
||||
"sender": sender,
|
||||
"sender_name": sender_name,
|
||||
"session_id": session_id,
|
||||
"files": files,
|
||||
},
|
||||
)
|
||||
elif isinstance(input_value, Record):
|
||||
input_value = records_to_text(template=record_template, records=input_value)
|
||||
if not input_value:
|
||||
input_value = ""
|
||||
if return_record and input_value_record:
|
||||
result: Union[Text, Record] = input_value_record
|
||||
if isinstance(input_value, Record):
|
||||
# Update the data of the record
|
||||
message = Message.from_record(input_value)
|
||||
else:
|
||||
result = input_value
|
||||
self.status = result
|
||||
if session_id and isinstance(result, (Record, str)):
|
||||
self.store_message(result, session_id, sender, sender_name)
|
||||
return result
|
||||
message = Message(
|
||||
text=input_value, sender=sender, sender_name=sender_name, files=files, session_id=session_id
|
||||
)
|
||||
self.status = message
|
||||
if session_id and isinstance(message, Message):
|
||||
self.store_message(message)
|
||||
return message
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Optional
|
|||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.helpers.record import records_to_text
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class TextComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class BaseMemoryComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ from typing import Optional, Union
|
|||
|
||||
from langchain_core.language_models.chat_models import BaseChatModel
|
||||
from langchain_core.language_models.llms import LLM
|
||||
from langchain_core.load import load
|
||||
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.field_typing.prompt import Prompt
|
||||
|
||||
|
||||
class LCModelComponent(CustomComponent):
|
||||
|
|
@ -85,7 +84,7 @@ class LCModelComponent(CustomComponent):
|
|||
return status_message
|
||||
|
||||
def get_chat_result(
|
||||
self, runnable: BaseChatModel, stream: bool, input_value: str | Record, system_message: Optional[str] = None
|
||||
self, runnable: BaseChatModel, stream: bool, input_value: str | Prompt, system_message: Optional[str] = None
|
||||
):
|
||||
messages: list[Union[HumanMessage, SystemMessage]] = []
|
||||
if not input_value and not system_message:
|
||||
|
|
@ -93,20 +92,21 @@ class LCModelComponent(CustomComponent):
|
|||
if system_message:
|
||||
messages.append(SystemMessage(content=system_message))
|
||||
if input_value:
|
||||
if isinstance(input_value, Record):
|
||||
if isinstance(input_value, Prompt):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
if "prompt" in input_value:
|
||||
prompt = load(input_value.prompt)
|
||||
prompt = input_value.load_lc_prompt()
|
||||
runnable = prompt | runnable
|
||||
else:
|
||||
messages.append(input_value.to_lc_message())
|
||||
else:
|
||||
messages.append(HumanMessage(content=input_value))
|
||||
inputs = messages or {}
|
||||
if stream:
|
||||
return runnable.stream(messages)
|
||||
return runnable.stream(inputs)
|
||||
else:
|
||||
message = runnable.invoke(messages)
|
||||
message = runnable.invoke(inputs)
|
||||
result = message.content
|
||||
if isinstance(message, AIMessage):
|
||||
status_message = self.build_status_message(message)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import base64
|
||||
from copy import deepcopy
|
||||
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.schema import Record
|
||||
from langflow.services.deps import get_storage_service
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
def record_to_string(record: Record) -> str:
|
||||
|
|
@ -20,7 +19,7 @@ def record_to_string(record: Record) -> str:
|
|||
return record.get_text()
|
||||
|
||||
|
||||
async def dict_values_to_string(d: dict) -> dict:
|
||||
def dict_values_to_string(d: dict) -> dict:
|
||||
"""
|
||||
Converts the values of a dictionary to strings.
|
||||
|
||||
|
|
@ -36,44 +35,21 @@ async def dict_values_to_string(d: dict) -> dict:
|
|||
# it could be a list of records or documents or strings
|
||||
if isinstance(value, list):
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, Record):
|
||||
d_copy[key][i] = item.to_lc_message()
|
||||
if isinstance(item, Message):
|
||||
d_copy[key][i] = item.text
|
||||
elif isinstance(item, Record):
|
||||
d_copy[key][i] = record_to_string(item)
|
||||
elif isinstance(item, Document):
|
||||
d_copy[key][i] = document_to_string(item)
|
||||
elif isinstance(value, Message):
|
||||
d_copy[key] = value.text
|
||||
elif isinstance(value, Record):
|
||||
if "files" in value and value.files:
|
||||
files = await get_file_paths(value.files)
|
||||
value.files = files
|
||||
d_copy[key] = value.to_lc_message()
|
||||
d_copy[key] = record_to_string(value)
|
||||
elif isinstance(value, Document):
|
||||
d_copy[key] = document_to_string(value)
|
||||
return d_copy
|
||||
|
||||
|
||||
async def get_file_paths(files: list[str]):
|
||||
storage_service = get_storage_service()
|
||||
file_paths = []
|
||||
for file in files:
|
||||
flow_id, file_name = file.split("/")
|
||||
file_paths.append(storage_service.build_full_path(flow_id=flow_id, file_name=file_name))
|
||||
return file_paths
|
||||
|
||||
|
||||
async def get_files(
|
||||
file_paths: str,
|
||||
convert_to_base64: bool = False,
|
||||
):
|
||||
storage_service = get_storage_service()
|
||||
file_objects = []
|
||||
for file_path in file_paths:
|
||||
flow_id, file_name = file_path.split("/")
|
||||
file_object = await storage_service.get_file(flow_id=flow_id, file_name=file_name)
|
||||
if convert_to_base64:
|
||||
file_object = base64.b64encode(file_object).decode("utf-8")
|
||||
file_objects.append(file_object)
|
||||
return file_objects
|
||||
|
||||
|
||||
def document_to_string(document: Document) -> str:
|
||||
"""
|
||||
Convert a document to a string.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from langchain_core.prompts import ChatPromptTemplate
|
|||
|
||||
from langflow.base.agents.agent import LCAgentComponent
|
||||
from langflow.field_typing import BaseLanguageModel, Text, Tool
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class ToolCallingAgentComponent(LCAgentComponent):
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ from typing import List, Optional
|
|||
from langchain.agents import create_xml_agent
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
|
||||
|
||||
from langflow.base.agents.agent import LCAgentComponent
|
||||
from langflow.field_typing import BaseLanguageModel, Text, Tool
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class XMLAgentComponent(LCAgentComponent):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from langchain_core.documents import Document
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, BaseRetriever, Text
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class RetrievalQAComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import uuid
|
|||
from typing import Any, Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
class WebhookComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from langchain_core.prompts.chat import HumanMessagePromptTemplate, SystemMessag
|
|||
from langflow.base.agents.agent import LCAgentComponent
|
||||
from langflow.base.agents.utils import AGENTS, AgentSpec, get_agents_list
|
||||
from langflow.field_typing import BaseLanguageModel, Text, Tool
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
class AgentComponent(LCAgentComponent):
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ from langflow.custom import CustomComponent
|
|||
from langflow.field_typing import Tool
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.helpers.flow import get_flow_inputs
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
class FlowToolComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import List, Optional
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.memory import get_messages, store_message
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class StoreMessageComponent(CustomComponent):
|
||||
|
|
@ -31,12 +31,11 @@ class StoreMessageComponent(CustomComponent):
|
|||
sender_name: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
message: str = "",
|
||||
) -> List[Record]:
|
||||
) -> List[Message]:
|
||||
store_message(
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
session_id=session_id,
|
||||
message=message,
|
||||
message=Message(
|
||||
text=message, sender=sender, sender_name=sender_name, flow_id=self.graph.flow_id, session_id=session_id
|
||||
)
|
||||
)
|
||||
|
||||
self.status = get_messages(session_id=session_id)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ from typing import Optional
|
|||
|
||||
from langflow.base.memory.memory import BaseMemoryComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.helpers.record import records_to_text
|
||||
from langflow.helpers.record import messages_to_text
|
||||
from langflow.memory import get_messages
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class MemoryComponent(BaseMemoryComponent):
|
||||
|
|
@ -43,7 +43,7 @@ class MemoryComponent(BaseMemoryComponent):
|
|||
},
|
||||
}
|
||||
|
||||
def get_messages(self, **kwargs) -> list[Record]:
|
||||
def get_messages(self, **kwargs) -> list[Message]:
|
||||
# Validate kwargs by checking if it contains the correct keys
|
||||
if "sender" not in kwargs:
|
||||
kwargs["sender"] = None
|
||||
|
|
@ -77,6 +77,6 @@ class MemoryComponent(BaseMemoryComponent):
|
|||
limit=n_messages,
|
||||
order=order,
|
||||
)
|
||||
messages_str = records_to_text(template=record_template or "", records=messages)
|
||||
messages_str = messages_to_text(template=record_template or "", messages=messages)
|
||||
self.status = messages_str
|
||||
return messages_str
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from langflow.base.io.chat import ChatComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class ChatInput(ChatComponent):
|
||||
|
|
@ -27,13 +26,11 @@ class ChatInput(ChatComponent):
|
|||
input_value: Optional[str] = None,
|
||||
files: Optional[list[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
return_record: Optional[bool] = False,
|
||||
) -> Union[Text, Record]:
|
||||
return super().build_no_record(
|
||||
) -> Message:
|
||||
return super().build_with_record(
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
input_value=input_value,
|
||||
files=files,
|
||||
session_id=session_id,
|
||||
return_record=return_record,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
from langchain_core.prompts import ChatPromptTemplate
|
||||
|
||||
from langflow.base.prompts.utils import dict_values_to_string
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Prompt, TemplateField, Text
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.field_typing import TemplateField
|
||||
from langflow.field_typing.prompt import Prompt
|
||||
|
||||
|
||||
class PromptComponent(CustomComponent):
|
||||
|
|
@ -21,10 +18,7 @@ class PromptComponent(CustomComponent):
|
|||
self,
|
||||
template: Prompt,
|
||||
**kwargs,
|
||||
) -> Record:
|
||||
prompt_template = ChatPromptTemplate.from_template(Text(template))
|
||||
kwargs = await dict_values_to_string(kwargs)
|
||||
messages = list(kwargs.values())
|
||||
prompt = prompt_template + messages
|
||||
self.status = f'Prompt:\n"{template}"'
|
||||
return Record(data={"prompt": prompt.to_json()})
|
||||
) -> Prompt:
|
||||
prompt = await Prompt.from_template_and_variables(template, kwargs)
|
||||
self.status = prompt.format_text()
|
||||
return prompt
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Optional
|
|||
from langchain_community.utilities.searchapi import SearchApiAPIWrapper
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory
|
|||
|
||||
from langflow.base.memory.memory import BaseMemoryComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class AstraDBMessageReaderComponent(BaseMemoryComponent):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain_astradb import AstraDBChatMessageHistory
|
||||
from langchain_core.messages import BaseMessage
|
||||
|
||||
from langflow.base.memory.memory import BaseMemoryComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_astradb import AstraDBChatMessageHistory
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class AstraDBMessageWriterComponent(BaseMemoryComponent):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from langchain_community.chat_message_histories.zep import SearchScope, SearchTy
|
|||
|
||||
from langflow.base.memory.memory import BaseMemoryComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class ZepMessageReaderComponent(BaseMemoryComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Optional
|
|||
|
||||
from langflow.base.memory.memory import BaseMemoryComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from zep_python.langchain import ZepChatMessageHistory
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class AmazonBedrockComponent(LCModelComponent):
|
|||
"advanced": True,
|
||||
},
|
||||
"cache": {"display_name": "Cache"},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"system_message": {
|
||||
"display_name": "System Message",
|
||||
"info": "System message to pass to the model.",
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class AnthropicLLM(LCModelComponent):
|
|||
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
|
||||
},
|
||||
"code": {"show": False},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"advanced": True,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class AzureChatOpenAIComponent(LCModelComponent):
|
|||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"code": {"show": False},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class QianfanChatEndpointComponent(LCModelComponent):
|
|||
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
|
||||
},
|
||||
"code": {"show": False},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class ChatLiteLLMModelComponent(LCModelComponent):
|
|||
"required": False,
|
||||
"default": False,
|
||||
},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class CohereComponent(LCModelComponent):
|
|||
"type": "float",
|
||||
"show": True,
|
||||
},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class HuggingFaceEndpointsComponent(LCModelComponent):
|
|||
"advanced": True,
|
||||
},
|
||||
"code": {"show": False},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class MistralAIModelComponent(LCModelComponent):
|
|||
|
||||
def build_config(self):
|
||||
return {
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"advanced": True,
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ class ChatOllamaComponent(LCModelComponent):
|
|||
"info": "Template to use for generating text.",
|
||||
"advanced": True,
|
||||
},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class OpenAIModelComponent(LCModelComponent):
|
|||
|
||||
def build_config(self):
|
||||
return {
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"advanced": True,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class ChatVertexAIComponent(LCModelComponent):
|
|||
"value": False,
|
||||
"advanced": True,
|
||||
},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record"]},
|
||||
"input_value": {"display_name": "Input", "input_types": ["Text", "Record", "Prompt"]},
|
||||
"stream": {
|
||||
"display_name": "Stream",
|
||||
"info": STREAM_INFO_TEXT,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from langflow.base.io.chat import ChatComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class ChatOutput(ChatComponent):
|
||||
|
|
@ -16,16 +15,12 @@ class ChatOutput(ChatComponent):
|
|||
sender_name: Optional[str] = "AI",
|
||||
input_value: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
return_record: Optional[bool] = False,
|
||||
record_template: Optional[str] = "{text}",
|
||||
files: Optional[list[str]] = None,
|
||||
) -> Union[Text, Record]:
|
||||
) -> Message:
|
||||
return super().build_with_record(
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
input_value=input_value,
|
||||
session_id=session_id,
|
||||
return_record=return_record,
|
||||
record_template=record_template or "",
|
||||
files=files,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import List
|
|||
from langchain_text_splitters import CharacterTextSplitter
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
from langflow.utils.util import unescape_string
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import List, Optional
|
|||
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Optional
|
|||
from langchain_community.utilities.searchapi import SearchApiAPIWrapper
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||
from langflow.components.vectorstores.Redis import RedisComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||
from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import List
|
||||
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||
from langflow.components.vectorstores.pgvector import PGVectorComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
from typing import List, Optional, Union
|
||||
|
||||
from langchain_astradb import AstraDBVectorStore
|
||||
from langchain_astradb.utils.astradb import SetupMode
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings, VectorStore
|
||||
from langflow.schema import Record
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
|
||||
|
||||
class AstraDBVectorStoreComponent(CustomComponent):
|
||||
|
|
@ -156,3 +157,4 @@ class AstraDBVectorStoreComponent(CustomComponent):
|
|||
)
|
||||
|
||||
return vector_store
|
||||
return vector_store
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from langchain_core.retrievers import BaseRetriever
|
|||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class ChromaComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
from typing import List, Optional, Union
|
||||
|
||||
from langchain_community.vectorstores import CouchbaseVectorStore
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings, VectorStore
|
||||
from langflow.schema import Record
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from couchbase.auth import PasswordAuthenticator # type: ignore
|
||||
from couchbase.cluster import Cluster # type: ignore
|
||||
from couchbase.options import ClusterOptions # type: ignore
|
||||
from langchain_community.vectorstores import CouchbaseVectorStore
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings, VectorStore
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class CouchbaseComponent(CustomComponent):
|
||||
display_name = "Couchbase"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain_core.vectorstores import VectorStore
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class FAISSComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from langchain_community.vectorstores.mongodb_atlas import MongoDBAtlasVectorSea
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class MongoDBAtlasComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from langchain_pinecone.vectorstores import PineconeVectorStore
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class PineconeComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain_core.vectorstores import VectorStore
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class QdrantComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain_core.retrievers import BaseRetriever
|
|||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class RedisComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from supabase.client import Client, create_client
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class SupabaseComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain_core.retrievers import BaseRetriever
|
|||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class UpstashVectorStoreComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from langchain_core.vectorstores import VectorStore
|
|||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseRetriever
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class VectaraComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from langchain_core.retrievers import BaseRetriever
|
|||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class WeaviateVectorStoreComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain_core.retrievers import BaseRetriever
|
|||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class PGVectorComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import yaml
|
|||
from cachetools import TTLCache, cachedmethod
|
||||
from langchain_core.documents import Document
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langflow.custom.code_parser.utils import (
|
||||
extract_inner_type_from_generic_alias,
|
||||
extract_union_types_from_generic_alias,
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ from .constants import (
|
|||
Embeddings,
|
||||
NestedDict,
|
||||
Object,
|
||||
Prompt,
|
||||
PromptTemplate,
|
||||
Text,
|
||||
TextSplitter,
|
||||
Tool,
|
||||
VectorStore,
|
||||
)
|
||||
from .prompt import Prompt
|
||||
from .range_spec import RangeSpec
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain.memory.chat_memory import BaseChatMemory
|
|||
from langchain_core.document_loaders import BaseLoader
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from langchain_core.language_models import BaseLLM, BaseLanguageModel
|
||||
from langchain_core.language_models import BaseLanguageModel, BaseLLM
|
||||
from langchain_core.memory import BaseMemory
|
||||
from langchain_core.output_parsers import BaseOutputParser
|
||||
from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||
|
|
@ -15,6 +15,8 @@ from langchain_core.tools import Tool
|
|||
from langchain_core.vectorstores import VectorStore
|
||||
from langchain_text_splitters import TextSplitter
|
||||
|
||||
from langflow.field_typing.prompt import Prompt
|
||||
|
||||
# Type alias for more complex dicts
|
||||
NestedDict = Dict[str, Union[str, Dict]]
|
||||
|
||||
|
|
@ -27,10 +29,6 @@ class Data:
|
|||
pass
|
||||
|
||||
|
||||
class Prompt:
|
||||
pass
|
||||
|
||||
|
||||
class Code:
|
||||
pass
|
||||
|
||||
|
|
|
|||
41
src/backend/base/langflow/field_typing/prompt.py
Normal file
41
src/backend/base/langflow/field_typing/prompt.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from langchain_core.load import load
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_core.prompts import BaseChatPromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||
|
||||
from langflow.base.prompts.utils import dict_values_to_string
|
||||
from langflow.schema.message import Message
|
||||
from langflow.schema.record import Record
|
||||
|
||||
|
||||
class Prompt(Record):
|
||||
def load_lc_prompt(self):
|
||||
if "prompt" not in self:
|
||||
raise ValueError("Prompt is required.")
|
||||
return load(self.prompt)
|
||||
|
||||
@classmethod
|
||||
def from_lc_prompt(
|
||||
cls,
|
||||
prompt: BaseChatPromptTemplate,
|
||||
):
|
||||
prompt_json = prompt.to_json()
|
||||
return cls(prompt=prompt_json)
|
||||
|
||||
def format_text(self):
|
||||
prompt_template = PromptTemplate.from_template(self.template)
|
||||
variables_with_str_values = dict_values_to_string(self.variables)
|
||||
formatted_prompt = prompt_template.format(**variables_with_str_values)
|
||||
return formatted_prompt
|
||||
|
||||
@classmethod
|
||||
async def from_template_and_variables(cls, template: str, variables: dict):
|
||||
instance = cls(template=template, variables=variables)
|
||||
contents = [{"type": "text", "text": instance.format_text()}]
|
||||
# Get all Message instances from the kwargs
|
||||
for value in variables.values():
|
||||
if isinstance(value, Message):
|
||||
content_dicts = await value.get_file_content_dicts()
|
||||
contents.extend(content_dicts)
|
||||
prompt_template = ChatPromptTemplate.from_messages([HumanMessage(content=contents)])
|
||||
instance.prompt = prompt_template.to_json()
|
||||
return instance
|
||||
|
|
@ -2,11 +2,11 @@ from enum import Enum
|
|||
from typing import Any, Generator, Union
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from langflow.schema.schema import Record
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class UnbuiltObject:
|
||||
|
|
@ -24,6 +24,7 @@ class ArtifactType(str, Enum):
|
|||
ARRAY = "array"
|
||||
STREAM = "stream"
|
||||
UNKNOWN = "unknown"
|
||||
MESSAGE = "message"
|
||||
|
||||
|
||||
def validate_prompt(prompt: str):
|
||||
|
|
@ -80,9 +81,14 @@ def get_artifact_type(custom_component, build_result) -> str:
|
|||
case list():
|
||||
result = ArtifactType.ARRAY
|
||||
|
||||
case Message():
|
||||
result = ArtifactType.MESSAGE
|
||||
|
||||
if result == ArtifactType.UNKNOWN:
|
||||
if isinstance(build_result, Generator):
|
||||
result = ArtifactType.STREAM
|
||||
elif isinstance(value, Message) and isinstance(value.text, Generator):
|
||||
result = ArtifactType.STREAM
|
||||
|
||||
return result.value
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import inspect
|
|||
import os
|
||||
import types
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional
|
||||
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Mapping, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -373,7 +373,7 @@ class Vertex:
|
|||
self.load_from_db_fields = load_from_db_fields
|
||||
self._raw_params = params.copy()
|
||||
|
||||
def update_raw_params(self, new_params: Dict[str, str | list[str]], overwrite: bool = False):
|
||||
def update_raw_params(self, new_params: Mapping[str, str | list[str]], overwrite: bool = False):
|
||||
"""
|
||||
Update the raw parameters of the vertex with the given new parameters.
|
||||
|
||||
|
|
@ -424,7 +424,7 @@ class Vertex:
|
|||
try:
|
||||
messages = [
|
||||
ChatOutputResponse(
|
||||
message=artifacts["message"],
|
||||
message=artifacts["text"],
|
||||
sender=artifacts.get("sender"),
|
||||
sender_name=artifacts.get("sender_name"),
|
||||
session_id=artifacts.get("session_id"),
|
||||
|
|
@ -444,7 +444,7 @@ class Vertex:
|
|||
# We need to set the artifacts to pass information
|
||||
# to the frontend
|
||||
self.set_artifacts()
|
||||
artifacts = self.artifacts
|
||||
artifacts = self.artifacts_raw
|
||||
if isinstance(artifacts, dict):
|
||||
messages = self.extract_messages_from_artifacts(artifacts)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, Interface
|
|||
from langflow.graph.utils import ArtifactType, UnbuiltObject, serialize_field
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME
|
||||
from langflow.services.monitor.utils import log_vertex_build
|
||||
from langflow.utils.schemas import ChatOutputResponse, RecordOutputResponse
|
||||
|
|
@ -98,11 +99,13 @@ class InterfaceVertex(Vertex):
|
|||
# Turn the dict into a pleasing to
|
||||
# read JSON inside a code block
|
||||
message = dict_to_codeblock(self._built_object)
|
||||
elif isinstance(self._built_object, Record):
|
||||
message = self._built_object.text
|
||||
elif isinstance(message, (AsyncIterator, Iterator)):
|
||||
stream_url = self.build_stream_url()
|
||||
message = ""
|
||||
elif isinstance(self._built_object, Message):
|
||||
if isinstance(message, (AsyncIterator, Iterator)):
|
||||
stream_url = self.build_stream_url()
|
||||
message = ""
|
||||
self._built_object.text = message
|
||||
else:
|
||||
message = self._built_object.text
|
||||
elif not isinstance(self._built_object, str):
|
||||
message = str(self._built_object)
|
||||
# if the message is a generator or iterator
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
from .record import docs_to_records, records_to_text
|
||||
from .record import docs_to_records, records_to_text, messages_to_text
|
||||
|
||||
__all__ = ["docs_to_records", "records_to_text"]
|
||||
__all__ = ["docs_to_records", "records_to_text", "messages_to_text"]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ from pydantic.v1 import BaseModel, Field, create_model
|
|||
from sqlmodel import Session, select
|
||||
|
||||
from langflow.graph.schema import RunOutputs
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME, Record
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.deps import get_session, get_settings_service, session_scope
|
||||
|
||||
|
|
@ -279,4 +280,4 @@ def generate_unique_flow_name(flow_name, user_id, session):
|
|||
|
||||
# If a flow with the name already exists, append (n) to the name and increment n
|
||||
flow_name = f"{original_name} ({n})"
|
||||
n += 1
|
||||
n += 1
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
from typing import Union
|
||||
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
def docs_to_records(documents: list[Document]) -> list[Record]:
|
||||
|
|
@ -27,7 +29,7 @@ def records_to_text(template: str, records: Union[Record, list[Record]]) -> str:
|
|||
Returns:
|
||||
list[str]: The converted list of texts.
|
||||
"""
|
||||
if isinstance(records, Record):
|
||||
if isinstance(records, (Record)):
|
||||
records = [records]
|
||||
# Check if there are any format strings in the template
|
||||
_records = []
|
||||
|
|
@ -39,3 +41,27 @@ def records_to_text(template: str, records: Union[Record, list[Record]]) -> str:
|
|||
|
||||
formated_records = [template.format(data=record.data, **record.data) for record in _records]
|
||||
return "\n".join(formated_records)
|
||||
|
||||
|
||||
def messages_to_text(template: str, messages: Union[Message, list[Message]]) -> str:
|
||||
"""
|
||||
Converts a list of Messages to a list of texts.
|
||||
|
||||
Args:
|
||||
messages (list[Message]): The list of Messages to convert.
|
||||
|
||||
Returns:
|
||||
list[str]: The converted list of texts.
|
||||
"""
|
||||
if isinstance(messages, (Message)):
|
||||
messages = [messages]
|
||||
# Check if there are any format strings in the template
|
||||
_messages = []
|
||||
for message in messages:
|
||||
# If it is not a message, create one with the key "text"
|
||||
if not isinstance(message, Message):
|
||||
raise ValueError("All elements in the list must be of type Message.")
|
||||
_messages.append(message)
|
||||
|
||||
formated_messages = [template.format(data=message.model_dump(), **message.model_dump()) for message in _messages]
|
||||
return "\n".join(formated_messages)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
|
@ -12,17 +13,14 @@ from emoji import demojize, purely_emoji # type: ignore
|
|||
from loguru import logger
|
||||
from sqlmodel import select
|
||||
|
||||
from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES
|
||||
from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES, ORJSON_OPTIONS
|
||||
from langflow.interface.types import get_all_components
|
||||
from langflow.services.auth.utils import create_super_user
|
||||
from langflow.services.database.models.flow.model import Flow, FlowCreate
|
||||
from langflow.services.database.models.folder.model import Folder, FolderCreate
|
||||
from langflow.services.database.models.user.crud import get_user_by_username
|
||||
from langflow.services.deps import get_settings_service, session_scope
|
||||
|
||||
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
|
||||
from langflow.services.deps import get_variable_service, get_storage_service
|
||||
|
||||
from langflow.services.database.models.user.crud import get_user_by_username
|
||||
from langflow.services.deps import get_settings_service, get_storage_service, get_variable_service, session_scope
|
||||
|
||||
STARTER_FOLDER_NAME = "Starter Projects"
|
||||
STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow."
|
||||
|
|
@ -75,10 +73,84 @@ def update_projects_components_with_latest_component_versions(project_data, all_
|
|||
}
|
||||
)
|
||||
node_data["template"][field_name][attr] = field_dict[attr]
|
||||
# Remove fields that are not in the latest template
|
||||
if node_data.get("display_name") != "Prompt":
|
||||
for field_name in list(node_data["template"].keys()):
|
||||
if field_name not in latest_template:
|
||||
node_data["template"].pop(field_name)
|
||||
log_node_changes(node_changes_log)
|
||||
return project_data_copy
|
||||
|
||||
|
||||
def update_edges_with_latest_component_versions(project_data):
|
||||
edge_changes_log = defaultdict(list)
|
||||
project_data_copy = deepcopy(project_data)
|
||||
for edge in project_data_copy.get("edges", []):
|
||||
source_handle = edge.get("data").get("sourceHandle")
|
||||
target_handle = edge.get("data").get("targetHandle")
|
||||
# Now find the source and target nodes in the nodes list
|
||||
source_node = next(
|
||||
(node for node in project_data.get("nodes", []) if node.get("id") == edge.get("source")), None
|
||||
)
|
||||
target_node = next(
|
||||
(node for node in project_data.get("nodes", []) if node.get("id") == edge.get("target")), None
|
||||
)
|
||||
if source_node and target_node:
|
||||
source_node_data = source_node.get("data").get("node")
|
||||
target_node_data = target_node.get("data").get("node")
|
||||
new_base_classes = source_node_data.get("base_classes")
|
||||
if source_handle["baseClasses"] != new_base_classes:
|
||||
edge_changes_log[source_node_data["display_name"]].append(
|
||||
{
|
||||
"attr": "baseClasses",
|
||||
"old_value": source_handle["baseClasses"],
|
||||
"new_value": new_base_classes,
|
||||
}
|
||||
)
|
||||
source_handle["baseClasses"] = new_base_classes
|
||||
|
||||
field_name = target_handle.get("fieldName")
|
||||
if field_name in target_node_data.get("template"):
|
||||
if target_handle["inputTypes"] != target_node_data.get("template").get(field_name).get("input_types"):
|
||||
edge_changes_log[target_node_data["display_name"]].append(
|
||||
{
|
||||
"attr": "inputTypes",
|
||||
"old_value": target_handle["inputTypes"],
|
||||
"new_value": target_node_data.get("template").get(field_name).get("input_types"),
|
||||
}
|
||||
)
|
||||
target_handle["inputTypes"] = target_node_data.get("template").get(field_name).get("input_types")
|
||||
escaped_source_handle = escape_json_dump(source_handle)
|
||||
escaped_target_handle = escape_json_dump(target_handle)
|
||||
if edge["sourceHandle"] != escaped_source_handle:
|
||||
edge_changes_log[source_node_data["display_name"]].append(
|
||||
{
|
||||
"attr": "sourceHandle",
|
||||
"old_value": edge["sourceHandle"],
|
||||
"new_value": escaped_source_handle,
|
||||
}
|
||||
)
|
||||
edge["sourceHandle"] = escaped_source_handle
|
||||
if edge["targetHandle"] != escaped_target_handle:
|
||||
edge_changes_log[target_node_data["display_name"]].append(
|
||||
{
|
||||
"attr": "targetHandle",
|
||||
"old_value": edge["targetHandle"],
|
||||
"new_value": escaped_target_handle,
|
||||
}
|
||||
)
|
||||
edge["targetHandle"] = escaped_target_handle
|
||||
|
||||
else:
|
||||
logger.error(f"Source or target node not found for edge: {edge}")
|
||||
log_node_changes(edge_changes_log)
|
||||
return project_data_copy
|
||||
|
||||
|
||||
def escape_json_dump(edge_dict):
|
||||
return json.dumps(edge_dict).replace('"', "œ")
|
||||
|
||||
|
||||
def log_node_changes(node_changes_log):
|
||||
# The idea here is to log the changes that were made to the nodes in debug
|
||||
# Something like:
|
||||
|
|
@ -155,7 +227,7 @@ def get_project_data(project):
|
|||
def update_project_file(project_path, project, updated_project_data):
|
||||
project["data"] = updated_project_data
|
||||
with open(project_path, "w", encoding="utf-8") as f:
|
||||
f.write(orjson.dumps(project, option=orjson.OPT_INDENT_2).decode())
|
||||
f.write(orjson.dumps(project, option=ORJSON_OPTIONS).decode())
|
||||
logger.info(f"Updated starter project {project['name']} file")
|
||||
|
||||
|
||||
|
|
@ -320,6 +392,7 @@ def create_or_update_starter_projects():
|
|||
updated_project_data = update_projects_components_with_latest_component_versions(
|
||||
project_data, all_types_dict
|
||||
)
|
||||
updated_project_data = update_edges_with_latest_component_versions(updated_project_data)
|
||||
if updated_project_data != project_data:
|
||||
project_data = updated_project_data
|
||||
# We also need to update the project data in the file
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,7 @@ from loguru import logger
|
|||
|
||||
from langflow.custom.eval import eval_custom_component_code
|
||||
from langflow.graph.utils import get_artifact_type, post_process_raw
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.custom import CustomComponent
|
||||
|
|
@ -130,10 +130,10 @@ async def instantiate_custom_component(params, user_id, vertex, fallback_to_env_
|
|||
if not isinstance(custom_repr, str):
|
||||
custom_repr = str(custom_repr)
|
||||
raw = custom_component.repr_value
|
||||
if hasattr(raw, "data"):
|
||||
if hasattr(raw, "data") and raw is not None:
|
||||
raw = raw.data
|
||||
|
||||
elif hasattr(raw, "model_dump"):
|
||||
elif hasattr(raw, "model_dump") and raw is not None:
|
||||
raw = raw.model_dump()
|
||||
|
||||
artifact_type = get_artifact_type(custom_component, build_result)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import warnings
|
||||
from typing import List, Optional, Union
|
||||
from typing import List, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
from langflow.services.deps import get_monitor_service
|
||||
from langflow.services.monitor.schema import MessageModel
|
||||
|
||||
|
|
@ -39,54 +39,46 @@ def get_messages(
|
|||
order=order,
|
||||
)
|
||||
|
||||
records: list[Record] = []
|
||||
messages: list[Message] = []
|
||||
# messages_df has a timestamp
|
||||
# it gets the last 5 messages, for example
|
||||
# but now they are ordered from most recent to least recent
|
||||
# so we need to reverse the order
|
||||
messages_df = messages_df[::-1] if order == "DESC" else messages_df
|
||||
for row in messages_df.itertuples():
|
||||
record = Record(
|
||||
data={
|
||||
"text": row.message,
|
||||
"sender": row.sender,
|
||||
"sender_name": row.sender_name,
|
||||
"session_id": row.session_id,
|
||||
"timestamp": row.timestamp,
|
||||
},
|
||||
)
|
||||
records.append(record)
|
||||
msg = Message(text=row.text, sender=row.sender, sender_name=row.sender_name, timestamp=row.timestamp)
|
||||
|
||||
return records
|
||||
messages.append(msg)
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def add_messages(records: Union[list[Record], Record], flow_id: Optional[str] = None):
|
||||
def add_messages(messages: Message | list[Message], flow_id: Optional[str] = None):
|
||||
"""
|
||||
Add a message to the monitor service.
|
||||
"""
|
||||
try:
|
||||
monitor_service = get_monitor_service()
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
if isinstance(records, Record):
|
||||
records = [records]
|
||||
if not all(isinstance(message, Message) for message in messages):
|
||||
types = ", ".join([str(type(message)) for message in messages])
|
||||
raise ValueError(f"The messages must be instances of Message. Found: {types}")
|
||||
|
||||
if not all(isinstance(record, (Record, str)) for record in records):
|
||||
types = ", ".join([str(type(record)) for record in records])
|
||||
raise ValueError(f"The records must be instances of Record. Found: {types}")
|
||||
messages_models: list[MessageModel] = []
|
||||
for msg in messages:
|
||||
msg.timestamp = monitor_service.get_timestamp()
|
||||
messages_models.append(MessageModel.from_message(msg, flow_id=flow_id))
|
||||
|
||||
messages: list[MessageModel] = []
|
||||
for record in records:
|
||||
record.timestamp = monitor_service.get_timestamp()
|
||||
messages.append(MessageModel.from_record(record, flow_id=flow_id))
|
||||
|
||||
for message in messages:
|
||||
for message_model in messages_models:
|
||||
try:
|
||||
monitor_service.add_message(message)
|
||||
monitor_service.add_message(message_model)
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding message to monitor service: {e}")
|
||||
logger.exception(e)
|
||||
raise e
|
||||
return records
|
||||
return messages_models
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise e
|
||||
|
|
@ -100,28 +92,22 @@ def delete_messages(session_id: str):
|
|||
session_id (str): The session ID associated with the messages to delete.
|
||||
"""
|
||||
monitor_service = get_monitor_service()
|
||||
monitor_service.delete_messages(session_id)
|
||||
monitor_service.delete_messages_session(session_id)
|
||||
|
||||
|
||||
def store_message(
|
||||
message: Union[str, Record],
|
||||
session_id: Optional[str] = None,
|
||||
sender: Optional[str] = None,
|
||||
sender_name: Optional[str] = None,
|
||||
message: Message,
|
||||
flow_id: Optional[str] = None,
|
||||
) -> List[Record]:
|
||||
) -> List[Message]:
|
||||
"""
|
||||
Stores a message in the memory.
|
||||
|
||||
Args:
|
||||
message (Union[str, Record]): The message to be stored. It can be either a string or a Record object.
|
||||
session_id (Optional[str]): The session ID associated with the message.
|
||||
sender (Optional[str]): The sender ID associated with the message.
|
||||
sender_name (Optional[str]): The name of the sender associated with the message.
|
||||
message (Message): The message to store.
|
||||
flow_id (Optional[str]): The flow ID associated with the message. When running from the CustomComponent you can access this using `self.graph.flow_id`.
|
||||
|
||||
Returns:
|
||||
List[Record]: A list of records containing the stored message.
|
||||
List[Message]: A list of records containing the stored message.
|
||||
|
||||
Raises:
|
||||
ValueError: If any of the required parameters (session_id, sender, sender_name) is not provided.
|
||||
|
|
@ -130,26 +116,7 @@ def store_message(
|
|||
warnings.warn("No message provided.")
|
||||
return []
|
||||
|
||||
if not session_id or not sender or not sender_name:
|
||||
if not message.session_id or not message.sender or not message.sender_name:
|
||||
raise ValueError("All of session_id, sender, and sender_name must be provided.")
|
||||
|
||||
if isinstance(message, Record):
|
||||
record = message
|
||||
record.data.update(
|
||||
{
|
||||
"session_id": session_id,
|
||||
"sender": sender,
|
||||
"sender_name": sender_name,
|
||||
}
|
||||
)
|
||||
elif isinstance(message, str):
|
||||
record = Record(
|
||||
data={
|
||||
"text": message,
|
||||
"session_id": session_id,
|
||||
"sender": sender,
|
||||
"sender_name": sender_name,
|
||||
},
|
||||
)
|
||||
|
||||
return add_messages([record], flow_id=flow_id)
|
||||
return add_messages([message], flow_id=flow_id)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from .dotdict import dotdict
|
||||
from .schema import Record
|
||||
from .record import Record
|
||||
|
||||
__all__ = ["Record", "dotdict"]
|
||||
|
|
|
|||
63
src/backend/base/langflow/schema/image.py
Normal file
63
src/backend/base/langflow/schema/image.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import base64
|
||||
|
||||
from PIL import Image as PILImage
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langflow.services.deps import get_storage_service
|
||||
|
||||
IMAGE_ENDPOINT = "/files/images/"
|
||||
|
||||
|
||||
def is_image_file(file_path):
|
||||
try:
|
||||
with PILImage.open(file_path) as img:
|
||||
img.verify() # Verify that it is, in fact, an image
|
||||
return True
|
||||
except (IOError, SyntaxError):
|
||||
return False
|
||||
|
||||
|
||||
async def get_file_paths(files: list[str]):
|
||||
storage_service = get_storage_service()
|
||||
file_paths = []
|
||||
for file in files:
|
||||
flow_id, file_name = file.split("/")
|
||||
file_paths.append(storage_service.build_full_path(flow_id=flow_id, file_name=file_name))
|
||||
return file_paths
|
||||
|
||||
|
||||
async def get_files(
|
||||
file_paths: list[str],
|
||||
convert_to_base64: bool = False,
|
||||
):
|
||||
storage_service = get_storage_service()
|
||||
file_objects: list[str | bytes] = []
|
||||
for file_path in file_paths:
|
||||
flow_id, file_name = file_path.split("/")
|
||||
file_object = await storage_service.get_file(flow_id=flow_id, file_name=file_name)
|
||||
if convert_to_base64:
|
||||
file_base64 = base64.b64encode(file_object).decode("utf-8")
|
||||
file_objects.append(file_base64)
|
||||
else:
|
||||
file_objects.append(file_object)
|
||||
return file_objects
|
||||
|
||||
|
||||
class Image(BaseModel):
|
||||
path: str | None = None
|
||||
url: str | None = None
|
||||
|
||||
def to_base64(self):
|
||||
if self.path:
|
||||
files = get_files([self.path], convert_to_base64=True)
|
||||
return files[0]
|
||||
raise ValueError("Image path is not set.")
|
||||
|
||||
def to_content_dict(self):
|
||||
return {
|
||||
"type": "image_url",
|
||||
"image_url": self.to_base64(),
|
||||
}
|
||||
|
||||
def get_url(self):
|
||||
return f"{IMAGE_ENDPOINT}{self.path}"
|
||||
111
src/backend/base/langflow/schema/message.py
Normal file
111
src/backend/base/langflow/schema/message.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
from datetime import datetime, timezone
|
||||
from typing import Annotated, Any, AsyncIterator, Iterator, Optional
|
||||
|
||||
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
||||
from langchain_core.prompt_values import ImagePromptValue
|
||||
from langchain_core.prompts.image import ImagePromptTemplate
|
||||
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, field_serializer
|
||||
|
||||
from langflow.schema.image import Image, get_file_paths, is_image_file
|
||||
from langflow.schema.record import Record
|
||||
|
||||
|
||||
def _timestamp_to_str(timestamp: datetime) -> str:
|
||||
return timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
class Message(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
# Helper class to deal with image data
|
||||
text: Optional[str | AsyncIterator | Iterator] = Field(default="")
|
||||
sender: str
|
||||
sender_name: str
|
||||
files: Optional[list[str | Image]] = Field(default=[])
|
||||
session_id: Optional[str] = Field(default="")
|
||||
timestamp: Annotated[str, BeforeValidator(_timestamp_to_str)] = Field(
|
||||
default=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
flow_id: Optional[str] = None
|
||||
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
new_files = []
|
||||
for file in self.files or []:
|
||||
if is_image_file(file):
|
||||
new_files.append(Image(path=file))
|
||||
else:
|
||||
new_files.append(file)
|
||||
self.files = new_files
|
||||
|
||||
def to_lc_message(
|
||||
self,
|
||||
) -> BaseMessage:
|
||||
"""
|
||||
Converts the Record to a BaseMessage.
|
||||
|
||||
Returns:
|
||||
BaseMessage: The converted BaseMessage.
|
||||
"""
|
||||
# The idea of this function is to be a helper to convert a Record to a BaseMessage
|
||||
# It will use the "sender" key to determine if the message is Human or AI
|
||||
# If the key is not present, it will default to AI
|
||||
# But first we check if all required keys are present in the data dictionary
|
||||
# they are: "text", "sender"
|
||||
if self.text is None or not self.sender:
|
||||
raise ValueError("Missing required keys ('text', 'sender') in Message.")
|
||||
|
||||
if self.sender == "User":
|
||||
if self.files:
|
||||
contents = [{"type": "text", "text": self.text}]
|
||||
contents.extend(self.get_file_content_dicts())
|
||||
human_message = HumanMessage(content=contents)
|
||||
else:
|
||||
human_message = HumanMessage(
|
||||
content=[{"type": "text", "text": self.text}],
|
||||
)
|
||||
|
||||
return human_message
|
||||
|
||||
return AIMessage(content=self.text)
|
||||
|
||||
@classmethod
|
||||
def from_record(cls, record: Record) -> "Message":
|
||||
"""
|
||||
Converts a BaseMessage to a Record.
|
||||
|
||||
Args:
|
||||
record (BaseMessage): The BaseMessage to convert.
|
||||
|
||||
Returns:
|
||||
Record: The converted Record.
|
||||
"""
|
||||
|
||||
return cls(
|
||||
text=record.text,
|
||||
sender=record.sender,
|
||||
sender_name=record.sender_name,
|
||||
files=record.files,
|
||||
session_id=record.session_id,
|
||||
timestamp=record.timestamp,
|
||||
flow_id=record.flow_id,
|
||||
)
|
||||
|
||||
@field_serializer("text", mode="plain")
|
||||
def serialize_text(self, value):
|
||||
if isinstance(value, AsyncIterator):
|
||||
return ""
|
||||
elif isinstance(value, Iterator):
|
||||
return ""
|
||||
return value
|
||||
|
||||
async def get_file_content_dicts(self):
|
||||
content_dicts = []
|
||||
files = await get_file_paths(self.files)
|
||||
|
||||
for file in files:
|
||||
if isinstance(file, Image):
|
||||
content_dicts.append(file.to_content_dict())
|
||||
else:
|
||||
image_template = ImagePromptTemplate()
|
||||
image_prompt_value: ImagePromptValue = image_template.invoke(input={"path": file})
|
||||
content_dicts.append({"type": "image_url", "image_url": image_prompt_value.image_url})
|
||||
return content_dicts
|
||||
202
src/backend/base/langflow/schema/record.py
Normal file
202
src/backend/base/langflow/schema/record.py
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import copy
|
||||
import json
|
||||
from typing import cast, Optional
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
||||
from langchain_core.prompts.image import ImagePromptTemplate
|
||||
from pydantic import BaseModel, model_serializer, model_validator
|
||||
from langchain_core.prompt_values import ImagePromptValue
|
||||
|
||||
|
||||
class Record(BaseModel):
|
||||
"""
|
||||
Represents a record with text and optional data.
|
||||
|
||||
Attributes:
|
||||
data (dict, optional): Additional data associated with the record.
|
||||
"""
|
||||
|
||||
text_key: str = "text"
|
||||
data: dict = {}
|
||||
default_value: Optional[str] = ""
|
||||
|
||||
@model_validator(mode="before")
|
||||
def validate_data(cls, values):
|
||||
if not values.get("data"):
|
||||
values["data"] = {}
|
||||
# Any other keyword should be added to the data dictionary
|
||||
for key in values:
|
||||
if key not in values["data"] and key not in {"text_key", "data", "default_value"}:
|
||||
values["data"][key] = values[key]
|
||||
return values
|
||||
|
||||
@model_serializer(mode="plain", when_used="json")
|
||||
def serialize_model(self):
|
||||
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
|
||||
return data
|
||||
|
||||
def get_text(self):
|
||||
"""
|
||||
Retrieves the text value from the data dictionary.
|
||||
|
||||
If the text key is present in the data dictionary, the corresponding value is returned.
|
||||
Otherwise, the default value is returned.
|
||||
|
||||
Returns:
|
||||
The text value from the data dictionary or the default value.
|
||||
"""
|
||||
return self.data.get(self.text_key, self.default_value)
|
||||
|
||||
@classmethod
|
||||
def from_document(cls, document: Document) -> "Record":
|
||||
"""
|
||||
Converts a Document to a Record.
|
||||
|
||||
Args:
|
||||
document (Document): The Document to convert.
|
||||
|
||||
Returns:
|
||||
Record: The converted Record.
|
||||
"""
|
||||
data = document.metadata
|
||||
data["text"] = document.page_content
|
||||
return cls(data=data, text_key="text")
|
||||
|
||||
@classmethod
|
||||
def from_lc_message(cls, message: BaseMessage) -> "Record":
|
||||
"""
|
||||
Converts a BaseMessage to a Record.
|
||||
|
||||
Args:
|
||||
message (BaseMessage): The BaseMessage to convert.
|
||||
|
||||
Returns:
|
||||
Record: The converted Record.
|
||||
"""
|
||||
data: dict = {"text": message.content}
|
||||
data["metadata"] = cast(dict, message.to_json())
|
||||
return cls(data=data, text_key="text")
|
||||
|
||||
def __add__(self, other: "Record") -> "Record":
|
||||
"""
|
||||
Combines the data of two records by attempting to add values for overlapping keys
|
||||
for all types that support the addition operation. Falls back to the value from 'other'
|
||||
record when addition is not supported.
|
||||
"""
|
||||
combined_data = self.data.copy()
|
||||
for key, value in other.data.items():
|
||||
# If the key exists in both records and both values support the addition operation
|
||||
if key in combined_data:
|
||||
try:
|
||||
combined_data[key] += value
|
||||
except TypeError:
|
||||
# Fallback: Use the value from 'other' record if addition is not supported
|
||||
combined_data[key] = value
|
||||
else:
|
||||
# If the key is not in the first record, simply add it
|
||||
combined_data[key] = value
|
||||
|
||||
return Record(data=combined_data)
|
||||
|
||||
def to_lc_document(self) -> Document:
|
||||
"""
|
||||
Converts the Record to a Document.
|
||||
|
||||
Returns:
|
||||
Document: The converted Document.
|
||||
"""
|
||||
text = self.data.pop(self.text_key, self.default_value)
|
||||
return Document(page_content=text, metadata=self.data)
|
||||
|
||||
def to_lc_message(
|
||||
self,
|
||||
) -> HumanMessage | SystemMessage:
|
||||
"""
|
||||
Converts the Record to a BaseMessage.
|
||||
|
||||
Returns:
|
||||
BaseMessage: The converted BaseMessage.
|
||||
"""
|
||||
# The idea of this function is to be a helper to convert a Record to a BaseMessage
|
||||
# It will use the "sender" key to determine if the message is Human or AI
|
||||
# If the key is not present, it will default to AI
|
||||
# But first we check if all required keys are present in the data dictionary
|
||||
# they are: "text", "sender"
|
||||
if not all(key in self.data for key in ["text", "sender"]):
|
||||
raise ValueError(f"Missing required keys ('text', 'sender') in Record: {self.data}")
|
||||
sender = self.data.get("sender", "Machine")
|
||||
text = self.data.get("text", "")
|
||||
files = self.data.get("files", [])
|
||||
if sender == "User":
|
||||
if files:
|
||||
contents = [{"type": "text", "text": text}]
|
||||
for file_path in files:
|
||||
image_template = ImagePromptTemplate()
|
||||
image_prompt_value: ImagePromptValue = image_template.invoke(input={"path": file_path})
|
||||
contents.append({"type": "image_url", "image_url": image_prompt_value.image_url})
|
||||
human_message = HumanMessage(content=contents)
|
||||
else:
|
||||
human_message = HumanMessage(
|
||||
content=[{"type": "text", "text": text}],
|
||||
)
|
||||
|
||||
return human_message
|
||||
|
||||
return AIMessage(content=text)
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""
|
||||
Allows attribute-like access to the data dictionary.
|
||||
"""
|
||||
try:
|
||||
if key.startswith("__"):
|
||||
return self.__getattribute__(key)
|
||||
if key in {"data", "text_key"} or key.startswith("_"):
|
||||
return super().__getattr__(key)
|
||||
|
||||
return self.data.get(key, self.default_value)
|
||||
except KeyError:
|
||||
# Fallback to default behavior to raise AttributeError for undefined attributes
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
"""
|
||||
Allows attribute-like setting of values in the data dictionary,
|
||||
while still allowing direct assignment to class attributes.
|
||||
"""
|
||||
if key in {"data", "text_key"} or key.startswith("_"):
|
||||
super().__setattr__(key, value)
|
||||
else:
|
||||
self.data[key] = value
|
||||
|
||||
def __delattr__(self, key):
|
||||
"""
|
||||
Allows attribute-like deletion from the data dictionary.
|
||||
"""
|
||||
if key in {"data", "text_key"} or key.startswith("_"):
|
||||
super().__delattr__(key)
|
||||
else:
|
||||
del self.data[key]
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
"""
|
||||
Custom deepcopy implementation to handle copying of the Record object.
|
||||
"""
|
||||
# Create a new Record object with a deep copy of the data dictionary
|
||||
return Record(data=copy.deepcopy(self.data, memo), text_key=self.text_key, default_value=self.default_value)
|
||||
|
||||
# check which attributes the Record has by checking the keys in the data dictionary
|
||||
def __dir__(self):
|
||||
return super().__dir__() + list(self.data.keys())
|
||||
|
||||
def __str__(self) -> str:
|
||||
# return a JSON string representation of the Record atributes
|
||||
try:
|
||||
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
|
||||
return json.dumps(data, indent=4)
|
||||
except Exception:
|
||||
return str(self.data)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.data
|
||||
|
|
@ -1,207 +1,7 @@
|
|||
import copy
|
||||
import json
|
||||
from typing import Literal, Optional, cast
|
||||
from typing import Literal
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
||||
from langchain_core.prompts.image import ImagePromptTemplate
|
||||
from pydantic import BaseModel, model_serializer, model_validator
|
||||
|
||||
|
||||
class Record(BaseModel):
|
||||
"""
|
||||
Represents a record with text and optional data.
|
||||
|
||||
Attributes:
|
||||
data (dict, optional): Additional data associated with the record.
|
||||
"""
|
||||
|
||||
text_key: str = "text"
|
||||
data: dict = {}
|
||||
default_value: Optional[str] = ""
|
||||
|
||||
@model_validator(mode="before")
|
||||
def validate_data(cls, values):
|
||||
if not values.get("data"):
|
||||
values["data"] = {}
|
||||
# Any other keyword should be added to the data dictionary
|
||||
for key in values:
|
||||
if key not in values["data"] and key not in {"text_key", "data", "default_value"}:
|
||||
values["data"][key] = values[key]
|
||||
return values
|
||||
|
||||
@model_serializer(mode="plain", when_used="json")
|
||||
def serialize_model(self):
|
||||
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
|
||||
return data
|
||||
|
||||
def get_text(self):
|
||||
"""
|
||||
Retrieves the text value from the data dictionary.
|
||||
|
||||
If the text key is present in the data dictionary, the corresponding value is returned.
|
||||
Otherwise, the default value is returned.
|
||||
|
||||
Returns:
|
||||
The text value from the data dictionary or the default value.
|
||||
"""
|
||||
return self.data.get(self.text_key, self.default_value)
|
||||
|
||||
@classmethod
|
||||
def from_document(cls, document: Document) -> "Record":
|
||||
"""
|
||||
Converts a Document to a Record.
|
||||
|
||||
Args:
|
||||
document (Document): The Document to convert.
|
||||
|
||||
Returns:
|
||||
Record: The converted Record.
|
||||
"""
|
||||
data = document.metadata
|
||||
data["text"] = document.page_content
|
||||
return cls(data=data, text_key="text")
|
||||
|
||||
@classmethod
|
||||
def from_lc_message(cls, message: BaseMessage) -> "Record":
|
||||
"""
|
||||
Converts a BaseMessage to a Record.
|
||||
|
||||
Args:
|
||||
message (BaseMessage): The BaseMessage to convert.
|
||||
|
||||
Returns:
|
||||
Record: The converted Record.
|
||||
"""
|
||||
data: dict = {"text": message.content}
|
||||
data["metadata"] = cast(dict, message.to_json())
|
||||
return cls(data=data, text_key="text")
|
||||
|
||||
def __add__(self, other: "Record") -> "Record":
|
||||
"""
|
||||
Combines the data of two records by attempting to add values for overlapping keys
|
||||
for all types that support the addition operation. Falls back to the value from 'other'
|
||||
record when addition is not supported.
|
||||
"""
|
||||
combined_data = self.data.copy()
|
||||
for key, value in other.data.items():
|
||||
# If the key exists in both records and both values support the addition operation
|
||||
if key in combined_data:
|
||||
try:
|
||||
combined_data[key] += value
|
||||
except TypeError:
|
||||
# Fallback: Use the value from 'other' record if addition is not supported
|
||||
combined_data[key] = value
|
||||
else:
|
||||
# If the key is not in the first record, simply add it
|
||||
combined_data[key] = value
|
||||
|
||||
return Record(data=combined_data)
|
||||
|
||||
def to_lc_document(self) -> Document:
|
||||
"""
|
||||
Converts the Record to a Document.
|
||||
|
||||
Returns:
|
||||
Document: The converted Document.
|
||||
"""
|
||||
text = self.data.pop(self.text_key, self.default_value)
|
||||
return Document(page_content=text, metadata=self.data)
|
||||
|
||||
def to_lc_message(
|
||||
self,
|
||||
) -> BaseMessage:
|
||||
"""
|
||||
Converts the Record to a BaseMessage.
|
||||
|
||||
Returns:
|
||||
BaseMessage: The converted BaseMessage.
|
||||
"""
|
||||
# The idea of this function is to be a helper to convert a Record to a BaseMessage
|
||||
# It will use the "sender" key to determine if the message is Human or AI
|
||||
# If the key is not present, it will default to AI
|
||||
# But first we check if all required keys are present in the data dictionary
|
||||
# they are: "text", "sender"
|
||||
if not all(key in self.data for key in ["text", "sender"]):
|
||||
raise ValueError(f"Missing required keys ('text', 'sender') in Record: {self.data}")
|
||||
sender = self.data.get("sender", "Machine")
|
||||
text = self.data.get("text", "")
|
||||
files = self.data.get("files", [])
|
||||
if sender == "User":
|
||||
if files:
|
||||
contents = [{"type": "text", "text": text}]
|
||||
for file_path in files:
|
||||
image_template = ImagePromptTemplate()
|
||||
image_prompt_value = image_template.invoke(input={"path": file_path})
|
||||
contents.append({"type": "image_url", "image_url": image_prompt_value.image_url})
|
||||
human_message = HumanMessage(content=contents)
|
||||
else:
|
||||
human_message = HumanMessage(
|
||||
content=[{"type": "text", "text": text}],
|
||||
)
|
||||
|
||||
return human_message
|
||||
|
||||
return AIMessage(content=text)
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""
|
||||
Allows attribute-like access to the data dictionary.
|
||||
"""
|
||||
try:
|
||||
if key.startswith("__"):
|
||||
return self.__getattribute__(key)
|
||||
if key in {"data", "text_key"} or key.startswith("_"):
|
||||
return super().__getattr__(key)
|
||||
|
||||
return self.data.get(key, self.default_value)
|
||||
except KeyError:
|
||||
# Fallback to default behavior to raise AttributeError for undefined attributes
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
"""
|
||||
Allows attribute-like setting of values in the data dictionary,
|
||||
while still allowing direct assignment to class attributes.
|
||||
"""
|
||||
if key in {"data", "text_key"} or key.startswith("_"):
|
||||
super().__setattr__(key, value)
|
||||
else:
|
||||
self.data[key] = value
|
||||
|
||||
def __delattr__(self, key):
|
||||
"""
|
||||
Allows attribute-like deletion from the data dictionary.
|
||||
"""
|
||||
if key in {"data", "text_key"} or key.startswith("_"):
|
||||
super().__delattr__(key)
|
||||
else:
|
||||
del self.data[key]
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
"""
|
||||
Custom deepcopy implementation to handle copying of the Record object.
|
||||
"""
|
||||
# Create a new Record object with a deep copy of the data dictionary
|
||||
return Record(data=copy.deepcopy(self.data, memo), text_key=self.text_key, default_value=self.default_value)
|
||||
|
||||
# check which attributes the Record has by checking the keys in the data dictionary
|
||||
def __dir__(self):
|
||||
return super().__dir__() + list(self.data.keys())
|
||||
|
||||
def __str__(self) -> str:
|
||||
# return a JSON string representation of the Record atributes
|
||||
try:
|
||||
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
|
||||
return json.dumps(data, indent=4)
|
||||
except Exception:
|
||||
return str(self.data)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.data
|
||||
|
||||
|
||||
INPUT_FIELD_NAME = "input_value"
|
||||
|
||||
InputType = Literal["chat", "text", "any"]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from pydantic import field_serializer, field_validator
|
|||
from sqlalchemy import UniqueConstraint
|
||||
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
||||
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.folder import Folder
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_serializer, field_validator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class TransactionModel(BaseModel):
|
||||
|
|
@ -77,7 +76,7 @@ class MessageModel(BaseModel):
|
|||
sender: str
|
||||
sender_name: str
|
||||
session_id: str
|
||||
message: str
|
||||
text: str
|
||||
files: list[str] = []
|
||||
|
||||
class Config:
|
||||
|
|
@ -91,18 +90,17 @@ class MessageModel(BaseModel):
|
|||
return v
|
||||
|
||||
@classmethod
|
||||
def from_record(cls, record: "Record", flow_id: Optional[str] = None):
|
||||
def from_message(cls, message: Message, flow_id: Optional[str] = None):
|
||||
# first check if the record has all the required fields
|
||||
if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
|
||||
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
|
||||
if not message.text or not message.sender or not message.sender_name:
|
||||
raise ValueError("The message does not have the required fields 'sender' and 'sender_name' in the data.")
|
||||
return cls(
|
||||
sender=record.sender,
|
||||
sender_name=record.sender_name,
|
||||
message=record.text,
|
||||
session_id=record.session_id,
|
||||
files=record.files or [],
|
||||
artifacts=record.artifacts or {},
|
||||
timestamp=record.timestamp,
|
||||
sender=message.sender,
|
||||
sender_name=message.sender_name,
|
||||
text=message.text,
|
||||
session_id=message.session_id,
|
||||
files=message.files or [],
|
||||
timestamp=message.timestamp,
|
||||
flow_id=flow_id,
|
||||
)
|
||||
|
||||
|
|
@ -121,7 +119,7 @@ class MessageModelResponse(MessageModel):
|
|||
|
||||
|
||||
class MessageModelRequest(MessageModel):
|
||||
message: str = Field(default="")
|
||||
text: str = Field(default="")
|
||||
sender: str = Field(default="")
|
||||
sender_name: str = Field(default="")
|
||||
session_id: str = Field(default="")
|
||||
|
|
|
|||
|
|
@ -92,8 +92,6 @@ class MonitorService(Service):
|
|||
with duckdb.connect(str(self.db_path)) as conn:
|
||||
df = conn.execute(query).df()
|
||||
|
||||
print(query)
|
||||
|
||||
return df.to_dict(orient="records")
|
||||
|
||||
def delete_vertex_builds(self, flow_id: Optional[str] = None):
|
||||
|
|
@ -134,7 +132,7 @@ class MonitorService(Service):
|
|||
order: Optional[str] = "DESC",
|
||||
limit: Optional[int] = None,
|
||||
):
|
||||
query = "SELECT index, flow_id, sender_name, sender, session_id, message, timestamp FROM messages"
|
||||
query = "SELECT index, flow_id, sender_name, sender, session_id, text, timestamp FROM messages"
|
||||
conditions = []
|
||||
if sender:
|
||||
conditions.append(f"sender = '{sender}'")
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ class DefaultPromptField(TemplateField):
|
|||
|
||||
advanced: bool = False
|
||||
multiline: bool = True
|
||||
input_types: list[str] = ["Document", "Record", "Text"]
|
||||
input_types: list[str] = ["Document", "Message", "Record", "Text"]
|
||||
value: str = "" # Set the value to empty string
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ from typing import Any, Dict, List, Optional, Union
|
|||
|
||||
from docstring_parser import parse
|
||||
|
||||
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.schema import Record
|
||||
from langflow.services.deps import get_settings_service
|
||||
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
|
||||
from langflow.utils import constants
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default function SwitchOutputView(nodeId): JSX.Element {
|
|||
const results = flowPoolNode?.data?.logs[0] ?? "";
|
||||
const resultType = results?.type;
|
||||
let resultMessage = results?.message;
|
||||
const RECORD_TYPES = ["record", "object", "array", "message"];
|
||||
if (resultMessage.raw) {
|
||||
resultMessage = resultMessage.raw;
|
||||
}
|
||||
|
|
@ -41,34 +42,20 @@ export default function SwitchOutputView(nodeId): JSX.Element {
|
|||
<TextOutputView left={false} value={resultMessage} />
|
||||
</Case>
|
||||
|
||||
<Case condition={resultType === "record"}>
|
||||
<Case condition={RECORD_TYPES.includes(resultType)}>
|
||||
<RecordsOutputComponent
|
||||
rows={[resultMessage] ?? []}
|
||||
rows={
|
||||
Array.isArray(resultMessage)
|
||||
? (resultMessage as Array<any>).every((item) => item.data)
|
||||
? (resultMessage as Array<any>).map((item) => item.data)
|
||||
: resultMessage
|
||||
: [resultMessage]
|
||||
}
|
||||
pagination={true}
|
||||
columnMode="union"
|
||||
/>
|
||||
</Case>
|
||||
|
||||
<Case condition={resultType === "object"}>
|
||||
<RecordsOutputComponent
|
||||
rows={[resultMessage]}
|
||||
pagination={true}
|
||||
columnMode="union"
|
||||
/>
|
||||
</Case>
|
||||
{Array.isArray(resultMessage) && (
|
||||
<Case condition={resultType === "array"}>
|
||||
<RecordsOutputComponent
|
||||
rows={
|
||||
(resultMessage as Array<any>).every((item) => item.data)
|
||||
? (resultMessage as Array<any>).map((item) => item.data)
|
||||
: resultMessage
|
||||
}
|
||||
pagination={true}
|
||||
columnMode="union"
|
||||
/>
|
||||
</Case>
|
||||
)}
|
||||
<Case condition={resultType === "stream"}>
|
||||
<div className="flex h-full w-full items-center justify-center align-middle">
|
||||
<Alert variant={"default"} className="w-fit">
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export async function sendAll(data: sendAllProps) {
|
|||
}
|
||||
|
||||
export async function postValidateCode(
|
||||
code: string,
|
||||
code: string
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ export async function postValidateCode(
|
|||
export async function postValidatePrompt(
|
||||
name: string,
|
||||
template: string,
|
||||
frontend_node: APIClassType,
|
||||
frontend_node: APIClassType
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return api.post(`${BASE_URL_API}validate/prompt`, {
|
||||
name,
|
||||
|
|
@ -151,7 +151,7 @@ export async function saveFlowToDatabase(newFlow: {
|
|||
* @throws Will throw an error if the update fails.
|
||||
*/
|
||||
export async function updateFlowInDatabase(
|
||||
updatedFlow: FlowType,
|
||||
updatedFlow: FlowType
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
||||
|
|
@ -329,7 +329,7 @@ export async function getHealth() {
|
|||
*
|
||||
*/
|
||||
export async function getBuildStatus(
|
||||
flowId: string,
|
||||
flowId: string
|
||||
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
|
||||
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
||||
}
|
||||
|
|
@ -342,7 +342,7 @@ export async function getBuildStatus(
|
|||
*
|
||||
*/
|
||||
export async function postBuildInit(
|
||||
flow: FlowType,
|
||||
flow: FlowType
|
||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
||||
}
|
||||
|
|
@ -358,7 +358,7 @@ export async function postBuildInit(
|
|||
*/
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
id: string,
|
||||
id: string
|
||||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
|
@ -380,7 +380,7 @@ export async function getProfilePictures(): Promise<ProfilePicturesTypeAPI | nul
|
|||
|
||||
export async function postCustomComponent(
|
||||
code: string,
|
||||
apiClass: APIClassType,
|
||||
apiClass: APIClassType
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
// let template = apiClass.template;
|
||||
return await api.post(`${BASE_URL_API}custom_component`, {
|
||||
|
|
@ -393,7 +393,7 @@ export async function postCustomComponentUpdate(
|
|||
code: string,
|
||||
template: APITemplateType,
|
||||
field: string,
|
||||
field_value: any,
|
||||
field_value: any
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
return await api.post(`${BASE_URL_API}custom_component/update`, {
|
||||
code,
|
||||
|
|
@ -415,7 +415,7 @@ export async function onLogin(user: LoginType) {
|
|||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
|
|
@ -477,11 +477,11 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
|
|||
|
||||
export async function getUsersPage(
|
||||
skip: number,
|
||||
limit: number,
|
||||
limit: number
|
||||
): Promise<Array<Users>> {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`,
|
||||
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -518,7 +518,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
|
|||
try {
|
||||
const res = await api.patch(
|
||||
`${BASE_URL_API}users/${user_id}/reset-password`,
|
||||
user,
|
||||
user
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -592,7 +592,7 @@ export async function saveFlowStore(
|
|||
last_tested_version?: string;
|
||||
},
|
||||
tags: string[],
|
||||
publicFlow = false,
|
||||
publicFlow = false
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.post(`${BASE_URL_API}store/components/`, {
|
||||
|
|
@ -721,7 +721,7 @@ export async function postStoreComponents(component: Component) {
|
|||
export async function getComponent(component_id: string) {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}store/components/${component_id}`,
|
||||
`${BASE_URL_API}store/components/${component_id}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -736,7 +736,7 @@ export async function searchComponent(
|
|||
page?: number | null,
|
||||
limit?: number | null,
|
||||
status?: string | null,
|
||||
tags?: string[],
|
||||
tags?: string[]
|
||||
): Promise<StoreComponentResponse | undefined> {
|
||||
try {
|
||||
let url = `${BASE_URL_API}store/components/`;
|
||||
|
|
@ -848,7 +848,7 @@ export async function updateFlowStore(
|
|||
},
|
||||
tags: string[],
|
||||
publicFlow = false,
|
||||
id: string,
|
||||
id: string
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`${BASE_URL_API}store/components/${id}`, {
|
||||
|
|
@ -932,7 +932,7 @@ export async function deleteGlobalVariable(id: string) {
|
|||
export async function updateGlobalVariable(
|
||||
name: string,
|
||||
value: string,
|
||||
id: string,
|
||||
id: string
|
||||
) {
|
||||
try {
|
||||
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
|
||||
|
|
@ -951,7 +951,7 @@ export async function getVerticesOrder(
|
|||
startNodeId?: string | null,
|
||||
stopNodeId?: string | null,
|
||||
nodes?: Node[],
|
||||
Edges?: Edge[],
|
||||
Edges?: Edge[]
|
||||
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
|
||||
// nodeId is optional and is a query parameter
|
||||
// if nodeId is not provided, the API will return all vertices
|
||||
|
|
@ -971,7 +971,7 @@ export async function getVerticesOrder(
|
|||
return await api.post(
|
||||
`${BASE_URL_API}build/${flowId}/vertices`,
|
||||
data,
|
||||
config,
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -979,16 +979,19 @@ export async function postBuildVertex(
|
|||
flowId: string,
|
||||
vertexId: string,
|
||||
input_value: string,
|
||||
files?: string[],
|
||||
files?: string[]
|
||||
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
||||
// input_value is optional and is a query parameter
|
||||
const data = { inputs: { input_value: input_value ?? "" } };
|
||||
let data = {};
|
||||
if (typeof input_value !== "undefined") {
|
||||
data["inputs"] = { input_value: input_value };
|
||||
}
|
||||
if (data && files) {
|
||||
data["files"] = files;
|
||||
}
|
||||
return await api.post(
|
||||
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
|
||||
data,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1012,7 +1015,7 @@ export async function getFlowPool({
|
|||
}
|
||||
|
||||
export async function deleteFlowPool(
|
||||
flowId: string,
|
||||
flowId: string
|
||||
): Promise<AxiosResponse<any>> {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: flowId };
|
||||
|
|
@ -1026,7 +1029,7 @@ export async function deleteFlowPool(
|
|||
* @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses.
|
||||
*/
|
||||
export async function multipleDeleteFlowsComponents(
|
||||
flowIds: string[],
|
||||
flowIds: string[]
|
||||
): Promise<AxiosResponse<any>[]> {
|
||||
const batches: string[][] = [];
|
||||
|
||||
|
|
@ -1049,7 +1052,7 @@ export async function multipleDeleteFlowsComponents(
|
|||
|
||||
// Execute all delete requests
|
||||
const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) =>
|
||||
deleteBatch(batch),
|
||||
deleteBatch(batch)
|
||||
);
|
||||
|
||||
// Return the responses after all requests are completed
|
||||
|
|
@ -1059,7 +1062,7 @@ export async function multipleDeleteFlowsComponents(
|
|||
export async function getTransactionTable(
|
||||
id: string,
|
||||
mode: "intersection" | "union",
|
||||
params = {},
|
||||
params = {}
|
||||
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: id };
|
||||
|
|
@ -1075,7 +1078,7 @@ export async function getMessagesTable(
|
|||
mode: "intersection" | "union",
|
||||
id?: string,
|
||||
excludedFields?: string[],
|
||||
params = {},
|
||||
params = {}
|
||||
): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
const config = {};
|
||||
if (id) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from unittest.mock import Mock, patch
|
|||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
from dictdiffer import diff
|
||||
from httpx import Response
|
||||
|
||||
from langflow.components import data
|
||||
|
|
@ -164,8 +165,8 @@ def test_directory_without_mocks():
|
|||
assert len(results) == len(projects)
|
||||
# each result is a Record that contains the content attribute
|
||||
# each are dict that are exactly the same as one of the projects
|
||||
for result in results:
|
||||
assert result.text in projects
|
||||
for i, result in enumerate(results):
|
||||
assert result.text in projects, list(diff(result.text, projects[i]))
|
||||
|
||||
# in ../docs/docs/components there are many mdx files
|
||||
# check if the directory component can load them
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ def test_successful_run_no_payload(client, starter_project, created_api_key):
|
|||
assert all(["ChatOutput" in _id for _id in ids])
|
||||
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
|
||||
assert all([name in display_names for name in ["Chat Output"]])
|
||||
inner_results = [output.get("results").get("result") for output in outputs_dict.get("outputs")]
|
||||
inner_results = [output.get("results").get("text") for output in outputs_dict.get("outputs")]
|
||||
|
||||
assert all([result is not None for result in inner_results]), inner_results
|
||||
|
||||
|
|
@ -478,7 +478,7 @@ def test_successful_run_with_output_type_text(client, starter_project, created_a
|
|||
assert all(["ChatOutput" in _id for _id in ids]), ids
|
||||
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
|
||||
assert all([name in display_names for name in ["Chat Output"]]), display_names
|
||||
inner_results = [output.get("results").get("result") for output in outputs_dict.get("outputs")]
|
||||
inner_results = [output.get("results").get("text") for output in outputs_dict.get("outputs")]
|
||||
expected_result = ""
|
||||
assert all([expected_result in result for result in inner_results]), inner_results
|
||||
|
||||
|
|
@ -509,7 +509,7 @@ def test_successful_run_with_output_type_any(client, starter_project, created_ap
|
|||
assert all(["ChatOutput" in _id or "TextOutput" in _id for _id in ids]), ids
|
||||
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
|
||||
assert all([name in display_names for name in ["Chat Output"]]), display_names
|
||||
inner_results = [output.get("results").get("result") for output in outputs_dict.get("outputs")]
|
||||
inner_results = [output.get("results").get("text") for output in outputs_dict.get("outputs")]
|
||||
expected_result = ""
|
||||
assert all([expected_result in result for result in inner_results]), inner_results
|
||||
|
||||
|
|
@ -567,7 +567,7 @@ def test_successful_run_with_input_type_text(client, starter_project, created_ap
|
|||
text_input_outputs = [output for output in outputs_dict.get("outputs") if "TextInput" in output.get("component_id")]
|
||||
assert len(text_input_outputs) == 0
|
||||
# Now we check if the input_value is correct
|
||||
assert all([output.get("results").get("result") == "value1" for output in text_input_outputs]), text_input_outputs
|
||||
assert all([output.get("results").get("text") == "value1" for output in text_input_outputs]), text_input_outputs
|
||||
|
||||
|
||||
# Now do the same for "chat" input type
|
||||
|
|
@ -598,7 +598,7 @@ def test_successful_run_with_input_type_chat(client, starter_project, created_ap
|
|||
chat_input_outputs = [output for output in outputs_dict.get("outputs") if "ChatInput" in output.get("component_id")]
|
||||
assert len(chat_input_outputs) == 1
|
||||
# Now we check if the input_value is correct
|
||||
assert all([output.get("results").get("result") == "value1" for output in chat_input_outputs]), chat_input_outputs
|
||||
assert all([output.get("results").get("text") == "value1" for output in chat_input_outputs]), chat_input_outputs
|
||||
|
||||
|
||||
def test_successful_run_with_input_type_any(client, starter_project, created_api_key):
|
||||
|
|
@ -632,7 +632,7 @@ def test_successful_run_with_input_type_any(client, starter_project, created_api
|
|||
]
|
||||
assert len(any_input_outputs) == 1
|
||||
# Now we check if the input_value is correct
|
||||
assert all([output.get("results").get("result") == "value1" for output in any_input_outputs]), any_input_outputs
|
||||
assert all([output.get("results").get("text") == "value1" for output in any_input_outputs]), any_input_outputs
|
||||
|
||||
|
||||
@pytest.mark.api_key_required
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue