lfx-valkey 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lfx_valkey/__init__.py +15 -0
- lfx_valkey/components/__init__.py +0 -0
- lfx_valkey/components/valkey/__init__.py +45 -0
- lfx_valkey/components/valkey/valkey.py +143 -0
- lfx_valkey/components/valkey/valkey_chat.py +62 -0
- lfx_valkey/extension.json +16 -0
- lfx_valkey-0.1.0.dist-info/METADATA +48 -0
- lfx_valkey-0.1.0.dist-info/RECORD +10 -0
- lfx_valkey-0.1.0.dist-info/WHEEL +4 -0
- lfx_valkey-0.1.0.dist-info/entry_points.txt +2 -0
lfx_valkey/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""lfx-valkey: Valkey bundle.
|
|
2
|
+
|
|
3
|
+
Distribution unit ``lfx-valkey``. At runtime Langflow's loader
|
|
4
|
+
discovers ``extension.json`` shipped alongside this ``__init__.py`` and
|
|
5
|
+
registers the bundle's components under the namespaced IDs
|
|
6
|
+
``ext:valkey:<Class>@official``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from lfx_valkey.components.valkey.valkey import ValkeyVectorStoreComponent
|
|
10
|
+
from lfx_valkey.components.valkey.valkey_chat import ValkeyIndexChatMemory
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ValkeyIndexChatMemory",
|
|
14
|
+
"ValkeyVectorStoreComponent",
|
|
15
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Lazy component re-exports for the ``valkey`` bundle.
|
|
2
|
+
|
|
3
|
+
Mirrors the pre-extraction layout of ``lfx.components.valkey`` so saved
|
|
4
|
+
flows that referenced the module-level class
|
|
5
|
+
(e.g. ``lfx.components.valkey.<Class>``) keep resolving via the
|
|
6
|
+
migration table after rewrite to
|
|
7
|
+
``lfx_valkey.components.valkey.<Class>``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from lfx.components._importing import import_mod
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .valkey import ValkeyVectorStoreComponent
|
|
18
|
+
from .valkey_chat import ValkeyIndexChatMemory
|
|
19
|
+
|
|
20
|
+
_dynamic_imports = {
|
|
21
|
+
"ValkeyIndexChatMemory": "valkey_chat",
|
|
22
|
+
"ValkeyVectorStoreComponent": "valkey",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ValkeyIndexChatMemory",
|
|
27
|
+
"ValkeyVectorStoreComponent",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def __getattr__(attr_name: str) -> Any:
|
|
32
|
+
if attr_name not in _dynamic_imports:
|
|
33
|
+
msg = f"module {__name__!r} has no attribute {attr_name!r}"
|
|
34
|
+
raise AttributeError(msg)
|
|
35
|
+
try:
|
|
36
|
+
result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
|
|
37
|
+
except (ModuleNotFoundError, ImportError, AttributeError) as e:
|
|
38
|
+
msg = f"Could not import {attr_name!r} from {__name__!r}: {e}"
|
|
39
|
+
raise AttributeError(msg) from e
|
|
40
|
+
globals()[attr_name] = result
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def __dir__() -> list[str]:
|
|
45
|
+
return list(__all__)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
|
|
3
|
+
from lfx.helpers.data import docs_to_data
|
|
4
|
+
from lfx.io import HandleInput, IntInput, SecretStrInput, StrInput
|
|
5
|
+
from lfx.schema.data import Data
|
|
6
|
+
|
|
7
|
+
logger = structlog.get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _patch_check_index_exists():
|
|
11
|
+
"""Patch langchain-aws check_index_exists to handle valkey-glide RequestError.
|
|
12
|
+
|
|
13
|
+
langchain-aws's check_index_exists only catches KeyError/ValueError/RuntimeError,
|
|
14
|
+
but valkey-glide raises glide_shared.exceptions.RequestError when an index doesn't
|
|
15
|
+
exist. This patch catches that specific exception so _create_index_if_not_exist
|
|
16
|
+
can proceed to create the index.
|
|
17
|
+
|
|
18
|
+
This is a workaround until langchain-aws handles the GLIDE exception itself.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
import langchain_aws.vectorstores.valkey.base as _valkey_base
|
|
22
|
+
except (ImportError, AttributeError):
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
if getattr(_valkey_base, "patched_check_index_exists", False):
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from glide_shared.exceptions import RequestError as GlideRequestError
|
|
30
|
+
except ImportError:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
_original_check = _valkey_base.check_index_exists
|
|
34
|
+
|
|
35
|
+
def _patched_check(client, index_name):
|
|
36
|
+
try:
|
|
37
|
+
return _original_check(client, index_name)
|
|
38
|
+
except GlideRequestError:
|
|
39
|
+
logger.debug("Valkey index does not exist yet", index_name=index_name)
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
_valkey_base.check_index_exists = _patched_check
|
|
43
|
+
_valkey_base.patched_check_index_exists = True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ValkeyVectorStoreComponent(LCVectorStoreComponent):
|
|
47
|
+
"""A custom component for implementing a Vector Store using Valkey."""
|
|
48
|
+
|
|
49
|
+
display_name: str = "Valkey"
|
|
50
|
+
description: str = "Implementation of Vector Store using Valkey"
|
|
51
|
+
name = "Valkey"
|
|
52
|
+
icon = "Valkey"
|
|
53
|
+
|
|
54
|
+
inputs = [
|
|
55
|
+
SecretStrInput(name="valkey_server_url", display_name="Valkey Server Connection String", required=True),
|
|
56
|
+
StrInput(name="valkey_index_name", display_name="Valkey Index"),
|
|
57
|
+
*LCVectorStoreComponent.inputs,
|
|
58
|
+
IntInput(
|
|
59
|
+
name="number_of_results",
|
|
60
|
+
display_name="Number of Results",
|
|
61
|
+
info="Number of results to return.",
|
|
62
|
+
value=4,
|
|
63
|
+
advanced=True,
|
|
64
|
+
),
|
|
65
|
+
HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
@check_cached_vector_store
|
|
69
|
+
def build_vector_store(self):
|
|
70
|
+
import json
|
|
71
|
+
|
|
72
|
+
# Imported lazily because it pulls valkey-glide, which ships no Windows
|
|
73
|
+
# wheels -- the ``valkey`` extra of langchain-aws is gated off Windows
|
|
74
|
+
# via the platform marker in this bundle's pyproject.toml. Keeping the
|
|
75
|
+
# import out of module scope lets the bundle load on Windows even though
|
|
76
|
+
# this vector store cannot run there; the Valkey Chat Memory component
|
|
77
|
+
# (redis-py based) still works.
|
|
78
|
+
try:
|
|
79
|
+
from langchain_aws.vectorstores import ValkeyVectorStore
|
|
80
|
+
except ImportError as e:
|
|
81
|
+
msg = (
|
|
82
|
+
"Could not import valkey-glide, which backs the Valkey Vector Store. "
|
|
83
|
+
"It ships no Windows wheels and is not installed on Windows; run this "
|
|
84
|
+
"component on Linux or macOS. Elsewhere, install it with "
|
|
85
|
+
"'pip install langchain-aws[valkey]'."
|
|
86
|
+
)
|
|
87
|
+
raise ImportError(msg) from e
|
|
88
|
+
|
|
89
|
+
_patch_check_index_exists()
|
|
90
|
+
|
|
91
|
+
self.ingest_data = self._prepare_ingest_data()
|
|
92
|
+
|
|
93
|
+
documents = []
|
|
94
|
+
for _input in self.ingest_data or []:
|
|
95
|
+
if isinstance(_input, Data):
|
|
96
|
+
documents.append(_input.to_lc_document())
|
|
97
|
+
else:
|
|
98
|
+
documents.append(_input)
|
|
99
|
+
|
|
100
|
+
# Filter out documents with empty page_content — embedding models
|
|
101
|
+
# (e.g. Bedrock) reject zero-length input strings.
|
|
102
|
+
valid_documents = []
|
|
103
|
+
for doc in documents:
|
|
104
|
+
content = getattr(doc, "page_content", None) or ""
|
|
105
|
+
if content.strip():
|
|
106
|
+
valid_documents.append(doc)
|
|
107
|
+
elif doc.metadata:
|
|
108
|
+
doc.page_content = json.dumps(doc.metadata, default=str)
|
|
109
|
+
valid_documents.append(doc)
|
|
110
|
+
else:
|
|
111
|
+
logger.warning("Valkey: skipping document with no content and no metadata")
|
|
112
|
+
documents = valid_documents
|
|
113
|
+
|
|
114
|
+
if not documents:
|
|
115
|
+
if not self.valkey_index_name:
|
|
116
|
+
msg = "If no documents are provided, an index name must be provided."
|
|
117
|
+
raise ValueError(msg)
|
|
118
|
+
|
|
119
|
+
return ValkeyVectorStore.from_existing_index(
|
|
120
|
+
embedding=self.embedding,
|
|
121
|
+
valkey_url=self.valkey_server_url,
|
|
122
|
+
index_name=self.valkey_index_name,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return ValkeyVectorStore.from_documents(
|
|
126
|
+
documents=documents,
|
|
127
|
+
embedding=self.embedding,
|
|
128
|
+
valkey_url=self.valkey_server_url,
|
|
129
|
+
index_name=self.valkey_index_name,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def search_documents(self) -> list[Data]:
|
|
133
|
+
vector_store = self.build_vector_store()
|
|
134
|
+
|
|
135
|
+
if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
|
|
136
|
+
docs = vector_store.similarity_search(
|
|
137
|
+
query=self.search_query,
|
|
138
|
+
k=self.number_of_results,
|
|
139
|
+
)
|
|
140
|
+
data = docs_to_data(docs)
|
|
141
|
+
self.status = data
|
|
142
|
+
return data
|
|
143
|
+
return []
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from urllib import parse
|
|
2
|
+
|
|
3
|
+
from lfx.base.memory.model import LCChatMemoryComponent
|
|
4
|
+
from lfx.field_typing.constants import Memory
|
|
5
|
+
from lfx.inputs.inputs import IntInput, MessageTextInput, SecretStrInput, StrInput
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ValkeyIndexChatMemory(LCChatMemoryComponent):
|
|
9
|
+
display_name = "Valkey Chat Memory"
|
|
10
|
+
description = "Retrieves and stores chat messages from Valkey."
|
|
11
|
+
name = "ValkeyChatMemory"
|
|
12
|
+
icon = "Valkey"
|
|
13
|
+
|
|
14
|
+
inputs = [
|
|
15
|
+
StrInput(
|
|
16
|
+
name="host", display_name="Hostname", required=True, value="localhost", info="IP address or hostname."
|
|
17
|
+
),
|
|
18
|
+
IntInput(name="port", display_name="Port", required=True, value=6379, info="Valkey Port Number."),
|
|
19
|
+
StrInput(name="database", display_name="Database", required=True, value="0", info="Valkey database."),
|
|
20
|
+
MessageTextInput(
|
|
21
|
+
name="username", display_name="Username", value="", info="The Valkey user name.", advanced=True
|
|
22
|
+
),
|
|
23
|
+
SecretStrInput(
|
|
24
|
+
name="password",
|
|
25
|
+
display_name="Valkey Password",
|
|
26
|
+
info="Password for the specified username.",
|
|
27
|
+
advanced=True,
|
|
28
|
+
load_from_db=False,
|
|
29
|
+
),
|
|
30
|
+
StrInput(name="key_prefix", display_name="Key prefix", info="Key prefix.", advanced=True),
|
|
31
|
+
MessageTextInput(
|
|
32
|
+
name="session_id", display_name="Session ID", info="Session ID for the message.", advanced=True
|
|
33
|
+
),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
def build_message_history(self) -> Memory:
|
|
37
|
+
from langchain_community.chat_message_histories.redis import RedisChatMessageHistory
|
|
38
|
+
|
|
39
|
+
kwargs = {}
|
|
40
|
+
if self.key_prefix:
|
|
41
|
+
kwargs["key_prefix"] = self.key_prefix
|
|
42
|
+
|
|
43
|
+
# Build URL, only include auth if credentials are actually provided
|
|
44
|
+
password = getattr(self, "password", None)
|
|
45
|
+
username = getattr(self, "username", None)
|
|
46
|
+
has_password = bool(password) and str(password).strip() not in ("", "None")
|
|
47
|
+
has_username = bool(username) and str(username).strip() not in ("", "None")
|
|
48
|
+
|
|
49
|
+
if has_password:
|
|
50
|
+
# Use quote(safe="") so every reserved char (space, +, /, :, @) is
|
|
51
|
+
# percent-encoded. redis-py's from_url parses the URL with urlparse
|
|
52
|
+
# and decodes credentials with unquote (not unquote_plus), so
|
|
53
|
+
# quote_plus would turn a space into "+" and round-trip to a wrong
|
|
54
|
+
# password.
|
|
55
|
+
encoded_pw = parse.quote(str(password), safe="")
|
|
56
|
+
encoded_username = parse.quote(str(username), safe="") if has_username else ""
|
|
57
|
+
auth = f"{encoded_username}:{encoded_pw}@"
|
|
58
|
+
else:
|
|
59
|
+
auth = ""
|
|
60
|
+
|
|
61
|
+
url = f"redis://{auth}{self.host}:{self.port}/{self.database}"
|
|
62
|
+
return RedisChatMessageHistory(session_id=self.session_id, url=url, **kwargs)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.langflow.org/extension/v1.json",
|
|
3
|
+
"id": "lfx-valkey",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"name": "Valkey",
|
|
6
|
+
"description": "Valkey component(s) as a standalone Langflow Extension Bundle.",
|
|
7
|
+
"lfx": {
|
|
8
|
+
"compat": ["1"]
|
|
9
|
+
},
|
|
10
|
+
"bundles": [
|
|
11
|
+
{
|
|
12
|
+
"name": "valkey",
|
|
13
|
+
"path": "components/valkey"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lfx-valkey
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Valkey component(s) as a standalone Langflow Extension Bundle.
|
|
5
|
+
Project-URL: Homepage, https://github.com/langflow-ai/langflow
|
|
6
|
+
Project-URL: Documentation, https://docs.langflow.org/extensions
|
|
7
|
+
Project-URL: Repository, https://github.com/langflow-ai/langflow
|
|
8
|
+
Author-email: Langflow <contact@langflow.org>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: bundle,extension,langflow,lfx,valkey
|
|
11
|
+
Requires-Python: <3.15,>=3.10
|
|
12
|
+
Requires-Dist: langchain-aws<2.0.0,>=1.4.1; sys_platform == 'win32'
|
|
13
|
+
Requires-Dist: langchain-aws[valkey]<2.0.0,>=1.4.1; sys_platform != 'win32'
|
|
14
|
+
Requires-Dist: langchain-community<1.0.0,>=0.4.1
|
|
15
|
+
Requires-Dist: lfx<2.0.0,>=1.11.0.dev0
|
|
16
|
+
Requires-Dist: redis<8.0.0,>=7.4.0
|
|
17
|
+
Requires-Dist: structlog<26.0.0,>=25.4.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# lfx-valkey
|
|
21
|
+
|
|
22
|
+
Valkey component(s) as a standalone Langflow Extension Bundle.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install lfx-valkey
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The bundle is registered automatically via the `langflow.extensions`
|
|
31
|
+
entry-point. After install, restart your Langflow server; the bundle's
|
|
32
|
+
components will appear in the palette under the `valkey` group with
|
|
33
|
+
the namespaced IDs `ext:valkey:<Class>@official`.
|
|
34
|
+
|
|
35
|
+
## Develop
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd src/bundles/valkey
|
|
39
|
+
pip install -e .
|
|
40
|
+
lfx extension validate src/lfx_valkey
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Migration
|
|
44
|
+
|
|
45
|
+
Saved flows referencing the legacy class name(s) or the old import paths
|
|
46
|
+
under `lfx.components.valkey.*` are rewritten to the new namespaced
|
|
47
|
+
IDs by the migration table in
|
|
48
|
+
`src/lfx/src/lfx/extension/migration/migration_table.json`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
lfx_valkey/__init__.py,sha256=dMLv1-FD9nXNuFmcmT0EhM144Q-jLin1liPr7q3VY7I,492
|
|
2
|
+
lfx_valkey/extension.json,sha256=0mA53PZCLS7awsSGVIkYSbrIivcg6cKCr_i2heNM2Jo,339
|
|
3
|
+
lfx_valkey/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
lfx_valkey/components/valkey/__init__.py,sha256=tlutRPy8SOLsiGIq5rP77JkEO12M1VTlhQTPrm10Y6E,1320
|
|
5
|
+
lfx_valkey/components/valkey/valkey.py,sha256=kQDK3HOoKUgYT5nNV5abAc0sF1CDxBscC567vOtdQ4Q,5445
|
|
6
|
+
lfx_valkey/components/valkey/valkey_chat.py,sha256=HO-EUS9304gVptBZFzZLeYG2NiHhzrinrDzn4eXQ3w0,2771
|
|
7
|
+
lfx_valkey-0.1.0.dist-info/METADATA,sha256=JtzAdg6NO4EMNJFfAz4y2iT62DkpGbRHg3uwmHCTS4Q,1537
|
|
8
|
+
lfx_valkey-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
9
|
+
lfx_valkey-0.1.0.dist-info/entry_points.txt,sha256=Qj9LWe52n0t0zzzYlzbau-b5QcboVEz4_h1XZETPTr4,46
|
|
10
|
+
lfx_valkey-0.1.0.dist-info/RECORD,,
|