diff --git a/poetry.lock b/poetry.lock index 942402398..0ee09bb52 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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] @@ -2623,13 +2623,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] @@ -4331,13 +4331,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] @@ -4417,13 +4417,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] @@ -4457,13 +4457,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] @@ -4483,12 +4483,12 @@ proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", " [[package]] name = "llama-cpp-python" -version = "0.2.77" +version = "0.2.78" description = "Python bindings for the llama.cpp library" optional = true python-versions = ">=3.8" files = [ - {file = "llama_cpp_python-0.2.77.tar.gz", hash = "sha256:5d2f87df941a72ad6d122c3ffd91d8fe58542db350bd169c07b025d625a26803"}, + {file = "llama_cpp_python-0.2.78.tar.gz", hash = "sha256:3df7cfde84287faaf29675fba8939060c3ab3f0ce8db875dabf7df5d83bd8751"}, ] [package.dependencies] @@ -4505,13 +4505,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] @@ -4525,7 +4525,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.26.0", markers = "python_version <= \"3.11\""}, + {version = ">=2.32.2", markers = "python_version > \"3.11\""}, +] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} Werkzeug = ">=2.0.0" @@ -5646,13 +5649,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] @@ -6334,13 +6337,13 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.46" +version = "3.0.47" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.46-py3-none-any.whl", hash = "sha256:45abe60a8300f3c618b23c16c4bb98c6fc80af8ce8b17c7ae92db48db3ee63c1"}, - {file = "prompt_toolkit-3.0.46.tar.gz", hash = "sha256:869c50d682152336e23c4db7f74667639b5047494202ffe7670817053fd57795"}, + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, ] [package.dependencies] @@ -9292,13 +9295,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]] @@ -9431,13 +9434,13 @@ six = "*" [[package]] name = "unstructured" -version = "0.14.4" +version = "0.14.5" description = "A library that prepares raw documents for downstream ML tasks." optional = false python-versions = "<3.13,>=3.9.0" files = [ - {file = "unstructured-0.14.4-py3-none-any.whl", hash = "sha256:d79104a574fd4de07548ee12ef63c34fab0a454a514d6252a261c7e8f15b9b1f"}, - {file = "unstructured-0.14.4.tar.gz", hash = "sha256:f4f3b5f18fcc174d3d606cbc3a9affa418494a8ea96424a112d0080fc9e93560"}, + {file = "unstructured-0.14.5-py3-none-any.whl", hash = "sha256:2286180ac089b691e1effb73c2ab17b7809011fbf7f751439301b12a4c984131"}, + {file = "unstructured-0.14.5.tar.gz", hash = "sha256:f37975273cdcf8f05768ef2f86abaf3d8f805992f6acb0441c2be00fa4ef6588"}, ] [package.dependencies] @@ -9465,7 +9468,7 @@ wrapt = "*" [package.extras] airtable = ["pyairtable"] -all-docs = ["effdet", "google-cloud-vision", "markdown", "msg-parser", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pikepdf", "pillow-heif", "pypandoc", "pypdf", "pytesseract", "python-docx (>=1.1.2)", "python-pptx (<=0.6.23)", "unstructured-inference (==0.7.33)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] +all-docs = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pikepdf", "pillow-heif", "pypandoc", "pypdf", "pytesseract", "python-docx (>=1.1.2)", "python-oxmsg", "python-pptx (<=0.6.23)", "unstructured-inference (==0.7.33)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] astra = ["astrapy"] azure = ["adlfs", "fsspec"] azure-cognitive-search = ["azure-search-documents"] @@ -9496,10 +9499,10 @@ hubspot = ["hubspot-api-client", "urllib3"] huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"] image = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pikepdf", "pillow-heif", "pypdf", "pytesseract", "unstructured-inference (==0.7.33)", "unstructured.pytesseract (>=0.3.12)"] jira = ["atlassian-python-api"] -local-inference = ["effdet", "google-cloud-vision", "markdown", "msg-parser", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pikepdf", "pillow-heif", "pypandoc", "pypdf", "pytesseract", "python-docx (>=1.1.2)", "python-pptx (<=0.6.23)", "unstructured-inference (==0.7.33)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] +local-inference = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pikepdf", "pillow-heif", "pypandoc", "pypdf", "pytesseract", "python-docx (>=1.1.2)", "python-oxmsg", "python-pptx (<=0.6.23)", "unstructured-inference (==0.7.33)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] md = ["markdown"] mongodb = ["pymongo"] -msg = ["msg-parser"] +msg = ["python-oxmsg"] notion = ["htmlBuilder", "notion-client"] odt = ["pypandoc", "python-docx (>=1.1.2)"] onedrive = ["Office365-REST-Python-Client", "bs4", "msal"] @@ -9529,13 +9532,13 @@ xlsx = ["networkx", "openpyxl", "pandas", "xlrd"] [[package]] name = "unstructured-client" -version = "0.23.1" +version = "0.23.2" description = "Python Client SDK for Unstructured API" optional = false python-versions = ">=3.8" files = [ - {file = "unstructured-client-0.23.1.tar.gz", hash = "sha256:844c0355e27d97ed87fa489fcf24e38f3c4d42fb8245645aa58629cbf5b8c08e"}, - {file = "unstructured_client-0.23.1-py3-none-any.whl", hash = "sha256:93a8729d9dfbe9fe18560288fcd69043946f85b4333ea1af189d5309a671008a"}, + {file = "unstructured-client-0.23.2.tar.gz", hash = "sha256:26864737a6c27471cba8bcb714e4e31038f62f84552b3ead5f241bc56fa42288"}, + {file = "unstructured_client-0.23.2-py3-none-any.whl", hash = "sha256:b3be91e7c2498aa9108a43dde4cbb75e450a87fabb450f0687c1900da7da73f6"}, ] [package.dependencies] @@ -10449,4 +10452,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "2a5cccb4fcd7feab46c9560d3add946ee2134ff225b8d393561ff2e874d2571a" +content-hash = "4e16ddf83311fa2c894623b76832a9dda98eec2b88975c087297364954dbdac6" diff --git a/pyproject.toml b/pyproject.toml index 58a6f2872..c766ac13f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,6 +86,7 @@ youtube-transcript-api = "^0.6.2" markdown = "^3.6" langchain-chroma = "^0.1.1" upstash-vector = "^0.4.0" +cassio = "^0.1.7" unstructured = {extras = ["docx", "md", "pptx"], version = "^0.14.4"} diff --git a/src/backend/base/langflow/components/memories/AstraDBMessageReader.py b/src/backend/base/langflow/components/memories/AstraDBMessageReader.py index bbb732f16..c61909887 100644 --- a/src/backend/base/langflow/components/memories/AstraDBMessageReader.py +++ b/src/backend/base/langflow/components/memories/AstraDBMessageReader.py @@ -1,9 +1,7 @@ from typing import Optional, cast -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 @@ -51,6 +49,14 @@ class AstraDBMessageReaderComponent(BaseMemoryComponent): Returns: list[Record]: A list of Record objects representing the search results. """ + try: + from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory + except ImportError: + raise ImportError( + "Could not import langchain Astra DB integration package. " + "Please install it with `pip install langchain-astradb`." + ) + memory: AstraDBChatMessageHistory = cast(AstraDBChatMessageHistory, kwargs.get("memory")) if not memory: raise ValueError("AstraDBChatMessageHistory instance is required.") @@ -63,14 +69,14 @@ class AstraDBMessageReaderComponent(BaseMemoryComponent): def build( self, - session_id: Text, + session_id: str, collection_name: str, token: str, api_endpoint: str, namespace: Optional[str] = None, ) -> list[Record]: try: - pass + from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory except ImportError: raise ImportError( "Could not import langchain Astra DB integration package. " diff --git a/src/backend/base/langflow/components/memories/AstraDBMessageWriter.py b/src/backend/base/langflow/components/memories/AstraDBMessageWriter.py index 265f60cf4..30c98e16c 100644 --- a/src/backend/base/langflow/components/memories/AstraDBMessageWriter.py +++ b/src/backend/base/langflow/components/memories/AstraDBMessageWriter.py @@ -1,11 +1,9 @@ from typing import Optional 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 class AstraDBMessageWriterComponent(BaseMemoryComponent): @@ -50,7 +48,7 @@ class AstraDBMessageWriterComponent(BaseMemoryComponent): self, sender: str, sender_name: str, - text: Text, + text: str, session_id: str, metadata: Optional[dict] = None, **kwargs, @@ -59,17 +57,27 @@ class AstraDBMessageWriterComponent(BaseMemoryComponent): Adds a message to the AstraDBChatMessageHistory memory. Args: - sender (Text): The type of the message sender. Valid values are "Machine" or "User". - sender_name (Text): The name of the message sender. - text (Text): The content of the message. - session_id (Text): The session ID associated with the message. + sender (str): The type of the message sender. Typically "ai" or "human". + sender_name (str): The name of the message sender. + text (str): The content of the message. + session_id (str): The session ID associated with the message. metadata (dict | None, optional): Additional metadata for the message. Defaults to None. - **kwargs: Additional keyword arguments. + **kwargs: Additional keyword arguments, including: + memory (AstraDBChatMessageHistory | None): The memory instance to add the message to. + Raises: ValueError: If the AstraDBChatMessageHistory instance is not provided. """ + try: + from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory + except ImportError: + raise ImportError( + "Could not import langchain Astra DB integration package. " + "Please install it with `pip install langchain-astradb`." + ) + memory: AstraDBChatMessageHistory | None = kwargs.pop("memory", None) if memory is None: raise ValueError("AstraDBChatMessageHistory instance is required.") @@ -89,14 +97,14 @@ class AstraDBMessageWriterComponent(BaseMemoryComponent): def build( self, input_value: Record, - session_id: Text, + session_id: str, collection_name: str, token: str, api_endpoint: str, namespace: Optional[str] = None, ) -> Record: try: - pass + from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory except ImportError: raise ImportError( "Could not import langchain Astra DB integration package. " diff --git a/src/backend/base/langflow/components/memories/CassandraMessageReader.py b/src/backend/base/langflow/components/memories/CassandraMessageReader.py new file mode 100644 index 000000000..3545d444a --- /dev/null +++ b/src/backend/base/langflow/components/memories/CassandraMessageReader.py @@ -0,0 +1,86 @@ +from typing import Optional, cast + +from langchain_community.chat_message_histories import CassandraChatMessageHistory + +from langflow.base.memory.memory import BaseMemoryComponent +from langflow.schema.schema import Record + + +class CassandraMessageReaderComponent(BaseMemoryComponent): + display_name = "Cassandra Message Reader" + description = "Retrieves stored chat messages from a Cassandra table on Astra DB." + + def build_config(self): + return { + "session_id": { + "display_name": "Session ID", + "info": "Session ID of the chat history.", + "input_types": ["Text"], + }, + "database_id": { + "display_name": "Database ID", + "info": "The Astra database ID.", + }, + "table_name": { + "display_name": "Table Name", + "info": "The name of the table where messages are stored.", + }, + "token": { + "display_name": "Token", + "info": "Authentication token for accessing Cassandra on Astra DB.", + "password": True, + }, + "keyspace": { + "display_name": "Keyspace", + "info": "Optional key space within Astra DB. The keyspace should already be created.", + "input_types": ["Text"], + "advanced": True, + }, + } + + def get_messages(self, **kwargs) -> list[Record]: + """ + Retrieves messages from the CassandraChatMessageHistory memory. + + Args: + memory (CassandraChatMessageHistory): The CassandraChatMessageHistory instance to retrieve messages from. + + Returns: + list[Record]: A list of Record objects representing the search results. + """ + memory: CassandraChatMessageHistory = cast(CassandraChatMessageHistory, kwargs.get("memory")) + if not memory: + raise ValueError("CassandraChatMessageHistory instance is required.") + + # Get messages from the memory + messages = memory.messages + results = [Record.from_lc_message(message) for message in messages] + + return list(results) + + def build( + self, + session_id: str, + table_name: str, + token: str, + database_id: str, + keyspace: Optional[str] = None, + ) -> list[Record]: + try: + import cassio + except ImportError: + raise ImportError( + "Could not import cassio integration package. " "Please install it with `pip install cassio`." + ) + + cassio.init(token=token, database_id=database_id) + memory = CassandraChatMessageHistory( + session_id=session_id, + table_name=table_name, + keyspace=keyspace, + ) + + records = self.get_messages(memory=memory) + self.status = records + + return records diff --git a/src/backend/base/langflow/components/memories/CassandraMessageWriter.py b/src/backend/base/langflow/components/memories/CassandraMessageWriter.py new file mode 100644 index 000000000..716364b64 --- /dev/null +++ b/src/backend/base/langflow/components/memories/CassandraMessageWriter.py @@ -0,0 +1,122 @@ +from typing import Optional + +from langflow.base.memory.memory import BaseMemoryComponent +from langflow.schema.schema import Record + +from langchain_core.messages import BaseMessage +from langchain_community.chat_message_histories import CassandraChatMessageHistory + + +class CassandraMessageWriterComponent(BaseMemoryComponent): + display_name = "Cassandra Message Writer" + description = "Writes a message to a Cassandra table on Astra DB." + + def build_config(self): + return { + "input_value": { + "display_name": "Input Record", + "info": "Record to write to Cassandra.", + }, + "session_id": { + "display_name": "Session ID", + "info": "Session ID of the chat history.", + "input_types": ["Text"], + }, + "database_id": { + "display_name": "Database ID", + "info": "The Astra database ID.", + }, + "table_name": { + "display_name": "Table Name", + "info": "The name of the table where messages will be stored.", + }, + "token": { + "display_name": "Token", + "info": "Authentication token for accessing Cassandra on Astra DB.", + "password": True, + }, + "keyspace": { + "display_name": "Keyspace", + "info": "Optional key space within Astra DB. The keyspace should already be created.", + "input_types": ["Text"], + "advanced": True, + }, + "ttl_seconds": { + "display_name": "TTL Seconds", + "info": "Optional time-to-live for the messages.", + "input_types": ["Number"], + "advanced": True, + }, + } + + def add_message( + self, + sender: str, + sender_name: str, + text: str, + session_id: str, + metadata: Optional[dict] = None, + **kwargs, + ): + """ + Adds a message to the CassandraChatMessageHistory memory. + + Args: + sender (str): The type of the message sender. Typically "ai" or "human". + sender_name (str): The name of the message sender. + text (str): The content of the message. + session_id (str): The session ID associated with the message. + metadata (dict | None, optional): Additional metadata for the message. Defaults to None. + **kwargs: Additional keyword arguments, including: + memory (CassandraChatMessageHistory | None): The memory instance to add the message to. + + + Raises: + ValueError: If the CassandraChatMessageHistory instance is not provided. + + """ + memory: CassandraChatMessageHistory | None = kwargs.pop("memory", None) + if memory is None: + raise ValueError("CassandraChatMessageHistory instance is required.") + + text_list = [ + BaseMessage( + content=text, + sender=sender, + sender_name=sender_name, + metadata=metadata, + session_id=session_id, + ) + ] + + memory.add_messages(text_list) + + def build( + self, + input_value: Record, + session_id: str, + table_name: str, + token: str, + database_id: str, + keyspace: Optional[str] = None, + ttl_seconds: Optional[int] = None, + ) -> Record: + try: + import cassio + except ImportError: + raise ImportError( + "Could not import cassio integration package. " "Please install it with `pip install cassio`." + ) + + cassio.init(token=token, database_id=database_id) + memory = CassandraChatMessageHistory( + session_id=session_id, + table_name=table_name, + keyspace=keyspace, + ttl_seconds=ttl_seconds, + ) + + self.add_message(**input_value.data, memory=memory) + self.status = f"Added message to Cassandra memory for session {session_id}" + + return input_value diff --git a/src/backend/base/langflow/components/vectorsearch/CassandraSearch.py b/src/backend/base/langflow/components/vectorsearch/CassandraSearch.py new file mode 100644 index 000000000..8ee558276 --- /dev/null +++ b/src/backend/base/langflow/components/vectorsearch/CassandraSearch.py @@ -0,0 +1,94 @@ +from typing import Any, List, Optional, Tuple + +from langflow.components.vectorstores.Cassandra import CassandraVectorStoreComponent +from langflow.components.vectorstores.base.model import LCVectorStoreComponent +from langflow.field_typing import Embeddings, Text +from langflow.schema import Record +from langchain_community.utilities.cassandra import SetupMode + + +class CassandraSearchComponent(LCVectorStoreComponent): + display_name = "Cassandra Search" + description = "Searches an existing Cassandra Vector Store." + icon = "Cassandra" + field_order = ["token", "database_id", "table_name", "input_value", "embedding"] + + def build_config(self): + return { + "search_type": { + "display_name": "Search Type", + "options": ["Similarity", "MMR"], + }, + "input_value": { + "display_name": "Input Value", + "info": "Input value to search", + }, + "embedding": {"display_name": "Embedding", "info": "Embedding to use"}, + "token": { + "display_name": "Token", + "info": "Authentication token for accessing Cassandra on Astra DB.", + "password": True, + }, + "database_id": { + "display_name": "Database ID", + "info": "The Astra database ID.", + }, + "table_name": { + "display_name": "Table Name", + "info": "The name of the table where vectors will be stored.", + }, + "keyspace": { + "display_name": "Keyspace", + "info": "Optional key space within Astra DB. The keyspace should already be created.", + "advanced": True, + }, + "body_index_options": { + "display_name": "Body Index Options", + "info": "Optional options used to create the body index.", + "advanced": True, + }, + "setup_mode": { + "display_name": "Setup Mode", + "info": "Configuration mode for setting up the Cassandra table, with options like 'Sync', 'Async', or 'Off'.", + "options": ["Sync", "Async", "Off"], + "advanced": True, + }, + "number_of_results": { + "display_name": "Number of Results", + "info": "Number of results to return.", + "advanced": True, + }, + } + + def build( + self, + embedding: Embeddings, + table_name: str, + input_value: Text, + token: str, + database_id: str, + search_type: str = "similarity", + number_of_results: int = 4, + keyspace: Optional[str] = None, + body_index_options: Optional[List[Tuple[str, Any]]] = None, + setup_mode: SetupMode = SetupMode.SYNC, + ) -> List[Record]: + vector_store = CassandraVectorStoreComponent().build( + embedding=embedding, + table_name=table_name, + token=token, + database_id=database_id, + keyspace=keyspace, + body_index_options=body_index_options, + setup_mode=setup_mode, + ) + + try: + return self.search_with_vector_store(input_value, search_type, vector_store, k=number_of_results) + except KeyError as e: + if "content" in str(e): + raise ValueError( + "You should ingest data through Langflow (or LangChain) to query it in Langflow. Your collection does not contain a field name 'content'." + ) + else: + raise e diff --git a/src/backend/base/langflow/components/vectorstores/AstraDB.py b/src/backend/base/langflow/components/vectorstores/AstraDB.py index 07ded028e..d13b489c6 100644 --- a/src/backend/base/langflow/components/vectorstores/AstraDB.py +++ b/src/backend/base/langflow/components/vectorstores/AstraDB.py @@ -1,6 +1,4 @@ from typing import List, Optional, Union -from langchain_astradb import AstraDBVectorStore -from langchain_astradb.utils.astradb import SetupMode from langflow.custom import CustomComponent from langflow.field_typing import Embeddings, VectorStore @@ -111,6 +109,15 @@ class AstraDBVectorStoreComponent(CustomComponent): metadata_indexing_exclude: Optional[List[str]] = None, collection_indexing_policy: Optional[dict] = None, ) -> Union[VectorStore, BaseRetriever]: + try: + from langchain_astradb import AstraDBVectorStore + from langchain_astradb.utils.astradb import SetupMode + except ImportError: + raise ImportError( + "Could not import langchain Astra DB integration package. " + "Please install it with `pip install langchain-astradb`." + ) + try: setup_mode_value = SetupMode[setup_mode.upper()] except KeyError: diff --git a/src/backend/base/langflow/components/vectorstores/Cassandra.py b/src/backend/base/langflow/components/vectorstores/Cassandra.py new file mode 100644 index 000000000..34c21ccd0 --- /dev/null +++ b/src/backend/base/langflow/components/vectorstores/Cassandra.py @@ -0,0 +1,110 @@ +from typing import Any, List, Optional, Tuple +from langchain_community.vectorstores import Cassandra +from langchain_community.utilities.cassandra import SetupMode + +from langflow.custom import CustomComponent +from langflow.field_typing import Embeddings, VectorStore +from langflow.schema import Record + + +class CassandraVectorStoreComponent(CustomComponent): + display_name = "Cassandra" + description = "Builds or loads a Cassandra Vector Store." + icon = "Cassandra" + field_order = ["token", "database_id", "table_name", "inputs", "embedding"] + + def build_config(self): + return { + "inputs": { + "display_name": "Inputs", + "info": "Optional list of records to be processed and stored in the vector store.", + }, + "embedding": {"display_name": "Embedding", "info": "Embedding to use"}, + "token": { + "display_name": "Token", + "info": "Authentication token for accessing Cassandra on Astra DB.", + "password": True, + }, + "database_id": { + "display_name": "Database ID", + "info": "The Astra database ID.", + }, + "table_name": { + "display_name": "Table Name", + "info": "The name of the table where vectors will be stored.", + }, + "keyspace": { + "display_name": "Keyspace", + "info": "Optional key space within Astra DB. The keyspace should already be created.", + "advanced": True, + }, + "ttl_seconds": { + "display_name": "TTL Seconds", + "info": "Optional time-to-live for the added texts.", + "advanced": True, + }, + "batch_size": { + "display_name": "Batch Size", + "info": "Optional number of records to process in a single batch.", + "advanced": True, + }, + "body_index_options": { + "display_name": "Body Index Options", + "info": "Optional options used to create the body index.", + "advanced": True, + }, + "setup_mode": { + "display_name": "Setup Mode", + "info": "Configuration mode for setting up the Cassandra table, with options like 'Sync', 'Async', or 'Off'.", + "options": ["Sync", "Async", "Off"], + "advanced": True, + }, + } + + def build( + self, + embedding: Embeddings, + token: str, + database_id: str, + inputs: Optional[List[Record]] = None, + keyspace: Optional[str] = None, + table_name: str = "", + ttl_seconds: Optional[int] = None, + batch_size: int = 16, + body_index_options: Optional[List[Tuple[str, Any]]] = None, + setup_mode: SetupMode = SetupMode.SYNC, + ) -> VectorStore: + try: + import cassio + except ImportError: + raise ImportError( + "Could not import cassio integration package. " "Please install it with `pip install cassio`." + ) + + cassio.init( + database_id=database_id, + token=token, + ) + + if inputs: + documents = [_input.to_lc_document() for _input in inputs] + table = Cassandra.from_documents( + documents=documents, + embedding=embedding, + table_name=table_name, + keyspace=keyspace, + ttl_seconds=ttl_seconds, + batch_size=batch_size, + body_index_options=body_index_options, + ) + else: + table = Cassandra( + embedding=embedding, + table_name=table_name, + keyspace=keyspace, + ttl_seconds=ttl_seconds, + body_index_options=body_index_options, + setup_mode=setup_mode, + ) + + return table diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index c809b6ad5..1f50cfedc 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -480,6 +480,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/src/frontend/src/icons/Cassandra/Cassandra.jsx b/src/frontend/src/icons/Cassandra/Cassandra.jsx new file mode 100644 index 000000000..9a8d48d38 --- /dev/null +++ b/src/frontend/src/icons/Cassandra/Cassandra.jsx @@ -0,0 +1,73 @@ +const CassandraSVG = (props) => ( + + + + + + + + + + + + + + + + + + + +); +export default CassandraSVG; diff --git a/src/frontend/src/icons/Cassandra/cassandra.svg b/src/frontend/src/icons/Cassandra/cassandra.svg new file mode 100644 index 000000000..6d6cdf024 --- /dev/null +++ b/src/frontend/src/icons/Cassandra/cassandra.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/frontend/src/icons/Cassandra/index.tsx b/src/frontend/src/icons/Cassandra/index.tsx new file mode 100644 index 000000000..7fe917b72 --- /dev/null +++ b/src/frontend/src/icons/Cassandra/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import CassandraSVG from "./Cassandra"; + +export const CassandraIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 329ca1fa0..d507aecd8 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -155,6 +155,7 @@ import { AstraDBIcon } from "../icons/AstraDB"; import { AzureIcon } from "../icons/Azure"; import { BingIcon } from "../icons/Bing"; import { BotMessageSquareIcon } from "../icons/BotMessageSquare"; +import { CassandraIcon } from "../icons/Cassandra"; import { ChromaIcon } from "../icons/ChromaIcon"; import { CohereIcon } from "../icons/Cohere"; import { CouchbaseIcon } from "../icons/Couchbase"; @@ -328,6 +329,7 @@ export const nodeIconsLucide: iconsType = { Play, Vectara: VectaraIcon, ArrowUpToLine: ArrowUpToLine, + Cassandra: CassandraIcon, Chroma: ChromaIcon, Couchbase: CouchbaseIcon, AirbyteJSONLoader: AirbyteIcon,