lfx-oracle 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_oracle/__init__.py +21 -0
- lfx_oracle/components/oracle/__init__.py +18 -0
- lfx_oracle/components/oracle/connection.py +83 -0
- lfx_oracle/components/oracle/oracledb_embeddings.py +70 -0
- lfx_oracle/components/oracle/oracledb_loaders.py +178 -0
- lfx_oracle/components/oracle/oraclevs.py +246 -0
- lfx_oracle/extension.json +16 -0
- lfx_oracle-0.1.0.dist-info/METADATA +46 -0
- lfx_oracle-0.1.0.dist-info/RECORD +11 -0
- lfx_oracle-0.1.0.dist-info/WHEEL +4 -0
- lfx_oracle-0.1.0.dist-info/entry_points.txt +2 -0
lfx_oracle/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""lfx-oracle: Oracle Database bundle.
|
|
2
|
+
|
|
3
|
+
Distribution unit ``lfx-oracle``. 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:oracle:<Class>@official``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from lfx_oracle.components.oracle.oracledb_embeddings import OracleEmbeddingsComponent
|
|
10
|
+
from lfx_oracle.components.oracle.oracledb_loaders import (
|
|
11
|
+
OracleAutonomousDatabaseLoaderComponent,
|
|
12
|
+
OracleDocLoaderComponent,
|
|
13
|
+
)
|
|
14
|
+
from lfx_oracle.components.oracle.oraclevs import OracleVectorStoreComponent
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"OracleAutonomousDatabaseLoaderComponent",
|
|
18
|
+
"OracleDocLoaderComponent",
|
|
19
|
+
"OracleEmbeddingsComponent",
|
|
20
|
+
"OracleVectorStoreComponent",
|
|
21
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Component re-exports for the ``oracle`` bundle.
|
|
2
|
+
|
|
3
|
+
Saved-flow migration entries that target the legacy in-tree
|
|
4
|
+
``lfx.components.oracledb.<Class>`` import paths resolve through this
|
|
5
|
+
package, so the moved Component class(es) must be importable from here
|
|
6
|
+
by name.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .oracledb_embeddings import OracleEmbeddingsComponent
|
|
10
|
+
from .oracledb_loaders import OracleAutonomousDatabaseLoaderComponent, OracleDocLoaderComponent
|
|
11
|
+
from .oraclevs import OracleVectorStoreComponent
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"OracleAutonomousDatabaseLoaderComponent",
|
|
15
|
+
"OracleDocLoaderComponent",
|
|
16
|
+
"OracleEmbeddingsComponent",
|
|
17
|
+
"OracleVectorStoreComponent",
|
|
18
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
_SENSITIVE_CONNECTION_PARAM_KEYS = {
|
|
7
|
+
"access_token",
|
|
8
|
+
"connection_string",
|
|
9
|
+
"credential",
|
|
10
|
+
"credentials",
|
|
11
|
+
"dsn",
|
|
12
|
+
"newpassword",
|
|
13
|
+
"passwd",
|
|
14
|
+
"password",
|
|
15
|
+
"private_key",
|
|
16
|
+
"proxy_user",
|
|
17
|
+
"pwd",
|
|
18
|
+
"secret",
|
|
19
|
+
"tns",
|
|
20
|
+
"token",
|
|
21
|
+
"user",
|
|
22
|
+
"username",
|
|
23
|
+
"wallet_password",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _is_sensitive_connection_param_key(key: str) -> bool:
|
|
28
|
+
normalized_key = re.sub(r"[^0-9a-zA-Z]+", "_", key.strip().lower()).strip("_")
|
|
29
|
+
compact_key = normalized_key.replace("_", "")
|
|
30
|
+
return any(
|
|
31
|
+
normalized_key == sensitive_key or compact_key == sensitive_key.replace("_", "")
|
|
32
|
+
for sensitive_key in _SENSITIVE_CONNECTION_PARAM_KEYS
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def split_credentialized_dsn(dsn: str) -> tuple[str | None, str | None, str]:
|
|
37
|
+
if "@" not in dsn:
|
|
38
|
+
return None, None, dsn
|
|
39
|
+
|
|
40
|
+
credentials, dsn_without_credentials = dsn.rsplit("@", 1)
|
|
41
|
+
if "/" not in credentials:
|
|
42
|
+
return None, None, dsn
|
|
43
|
+
|
|
44
|
+
user, password = credentials.split("/", 1)
|
|
45
|
+
return user or None, password or None, dsn_without_credentials
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_connection_params(
|
|
49
|
+
connection_params: dict[str, Any] | None = None,
|
|
50
|
+
*,
|
|
51
|
+
user: str | None = None,
|
|
52
|
+
password: str | None = None,
|
|
53
|
+
dsn: str | None = None,
|
|
54
|
+
wallet_password: str | None = None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
raw_params = {key: value for key, value in (connection_params or {}).items() if value not in (None, "")}
|
|
57
|
+
sensitive_keys = sorted(key for key in raw_params if _is_sensitive_connection_param_key(key))
|
|
58
|
+
if sensitive_keys:
|
|
59
|
+
keys = ", ".join(sensitive_keys)
|
|
60
|
+
msg = (
|
|
61
|
+
f"connection_params contains sensitive keys: {keys}. "
|
|
62
|
+
"Use the dedicated secret fields for user, password, dsn, wallet_password, and proxy."
|
|
63
|
+
)
|
|
64
|
+
raise ValueError(msg)
|
|
65
|
+
|
|
66
|
+
if isinstance(dsn, str) and (not user or not password):
|
|
67
|
+
parsed_user, parsed_password, parsed_dsn = split_credentialized_dsn(dsn)
|
|
68
|
+
user = user or parsed_user
|
|
69
|
+
password = password or parsed_password
|
|
70
|
+
dsn = parsed_dsn
|
|
71
|
+
|
|
72
|
+
params = dict(raw_params)
|
|
73
|
+
|
|
74
|
+
if user:
|
|
75
|
+
params["user"] = user
|
|
76
|
+
if password:
|
|
77
|
+
params["password"] = password
|
|
78
|
+
if dsn:
|
|
79
|
+
params["dsn"] = dsn
|
|
80
|
+
if wallet_password:
|
|
81
|
+
params["wallet_password"] = wallet_password
|
|
82
|
+
|
|
83
|
+
return params
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import oracledb
|
|
5
|
+
from langchain_oracledb import OracleEmbeddings
|
|
6
|
+
from lfx.base.models.model import LCModelComponent
|
|
7
|
+
from lfx.field_typing import Embeddings
|
|
8
|
+
from lfx.io import DictInput, Output, SecretStrInput
|
|
9
|
+
|
|
10
|
+
from .connection import build_connection_params
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OracleEmbeddingsComponent(LCModelComponent):
|
|
14
|
+
display_name = "Oracle Embeddings"
|
|
15
|
+
description = "Generate embeddings using Oracle AI Vector Search."
|
|
16
|
+
icon = "Oracle"
|
|
17
|
+
name = "OracleEmbeddings"
|
|
18
|
+
|
|
19
|
+
inputs = [
|
|
20
|
+
SecretStrInput(name="user", display_name="User", required=False),
|
|
21
|
+
SecretStrInput(name="password", display_name="Password", required=False),
|
|
22
|
+
SecretStrInput(name="dsn", display_name="DSN", required=True),
|
|
23
|
+
SecretStrInput(name="wallet_password", display_name="Wallet Password", required=False, advanced=True),
|
|
24
|
+
DictInput(
|
|
25
|
+
name="connection_params",
|
|
26
|
+
display_name="Additional Connection Parameters",
|
|
27
|
+
info="Non-secret arguments passed to python-oracledb connect(), such as config_dir and wallet_location.",
|
|
28
|
+
list=True,
|
|
29
|
+
required=False,
|
|
30
|
+
advanced=True,
|
|
31
|
+
),
|
|
32
|
+
DictInput(
|
|
33
|
+
name="embedding_params",
|
|
34
|
+
display_name="Embedding Parameters",
|
|
35
|
+
list=True,
|
|
36
|
+
info=(
|
|
37
|
+
"https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/utl_to_embedding-and-utl_to_embeddings-dbms_vector.html"
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
SecretStrInput(name="proxy", display_name="Proxy", required=False, advanced=True),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
outputs = [
|
|
44
|
+
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def build_template_config(self) -> dict[str, Any]:
|
|
48
|
+
return self.get_template_config(self)
|
|
49
|
+
|
|
50
|
+
def build_embeddings(self) -> Embeddings:
|
|
51
|
+
connection_params = build_connection_params(
|
|
52
|
+
self.connection_params,
|
|
53
|
+
user=self.user,
|
|
54
|
+
password=self.password,
|
|
55
|
+
dsn=self.dsn,
|
|
56
|
+
wallet_password=self.wallet_password,
|
|
57
|
+
)
|
|
58
|
+
connection = oracledb.connect(**connection_params)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
return OracleEmbeddings(
|
|
62
|
+
conn=connection,
|
|
63
|
+
params=self.embedding_params,
|
|
64
|
+
proxy=self.proxy or None,
|
|
65
|
+
)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
with suppress(Exception):
|
|
68
|
+
connection.close()
|
|
69
|
+
msg = "Unable to create OracleEmbeddings."
|
|
70
|
+
raise ValueError(msg) from e
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import oracledb
|
|
6
|
+
from langchain_oracledb.document_loaders import OracleAutonomousDatabaseLoader, OracleDocLoader
|
|
7
|
+
from lfx.custom.custom_component.component import Component
|
|
8
|
+
from lfx.io import DictInput, MultilineInput, Output, SecretStrInput, StrInput
|
|
9
|
+
from lfx.schema.data import Data
|
|
10
|
+
|
|
11
|
+
from .connection import build_connection_params, split_credentialized_dsn
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OracleDocLoaderComponent(Component):
|
|
15
|
+
display_name = "Oracle Doc Loader"
|
|
16
|
+
description = "Read documents from Oracle Database using OracleDocLoader."
|
|
17
|
+
documentation = "https://docs.langchain.com/oss/python/integrations/document_loaders/oracleai"
|
|
18
|
+
trace_type = "tool"
|
|
19
|
+
icon = "Oracle"
|
|
20
|
+
name = "OracleDocLoader"
|
|
21
|
+
|
|
22
|
+
inputs = [
|
|
23
|
+
SecretStrInput(name="user", display_name="User", required=False),
|
|
24
|
+
SecretStrInput(name="password", display_name="Password", required=False),
|
|
25
|
+
SecretStrInput(name="dsn", display_name="DSN", required=True),
|
|
26
|
+
SecretStrInput(name="wallet_password", display_name="Wallet Password", required=False, advanced=True),
|
|
27
|
+
DictInput(
|
|
28
|
+
name="connection_params",
|
|
29
|
+
display_name="Additional Connection Parameters",
|
|
30
|
+
info="Non-secret arguments passed to python-oracledb connect(), such as config_dir and wallet_location.",
|
|
31
|
+
required=False,
|
|
32
|
+
advanced=True,
|
|
33
|
+
),
|
|
34
|
+
DictInput(
|
|
35
|
+
name="params",
|
|
36
|
+
display_name="Loader Parameters",
|
|
37
|
+
info="Arguments passed to langchain-oracledb OracleDocLoader.",
|
|
38
|
+
required=True,
|
|
39
|
+
),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
outputs = [
|
|
43
|
+
Output(name="data", display_name="JSON", method="load_documents"),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
def build_template_config(self) -> dict[str, Any]:
|
|
47
|
+
return self.get_template_config(self)
|
|
48
|
+
|
|
49
|
+
def load_documents(self) -> list[Data]:
|
|
50
|
+
connection_params = build_connection_params(
|
|
51
|
+
self.connection_params,
|
|
52
|
+
user=self.user,
|
|
53
|
+
password=self.password,
|
|
54
|
+
dsn=self.dsn,
|
|
55
|
+
wallet_password=self.wallet_password,
|
|
56
|
+
)
|
|
57
|
+
with oracledb.connect(**connection_params) as connection:
|
|
58
|
+
loader = OracleDocLoader(connection, self.params)
|
|
59
|
+
data = [Data.from_document(doc) for doc in loader.load()]
|
|
60
|
+
|
|
61
|
+
self.status = data
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class OracleAutonomousDatabaseLoaderComponent(Component):
|
|
66
|
+
display_name = "Oracle Autonomous Database Loader"
|
|
67
|
+
description = "Load rows from Oracle Autonomous Database as documents."
|
|
68
|
+
documentation = (
|
|
69
|
+
"https://github.com/oracle/langchain-oracle/blob/main/libs/oracledb/"
|
|
70
|
+
"langchain_oracledb/document_loaders/oracleadb_loader.py"
|
|
71
|
+
)
|
|
72
|
+
trace_type = "tool"
|
|
73
|
+
icon = "Oracle"
|
|
74
|
+
name = "OracleAutonomousDatabaseLoader"
|
|
75
|
+
|
|
76
|
+
inputs = [
|
|
77
|
+
StrInput(
|
|
78
|
+
name="query",
|
|
79
|
+
display_name="SQL Query",
|
|
80
|
+
required=True,
|
|
81
|
+
info="SQL query to execute. Each row becomes a document.",
|
|
82
|
+
),
|
|
83
|
+
SecretStrInput(name="user", display_name="User", required=False),
|
|
84
|
+
SecretStrInput(name="password", display_name="Password", required=False),
|
|
85
|
+
SecretStrInput(name="dsn", display_name="DSN", required=True),
|
|
86
|
+
SecretStrInput(name="wallet_password", display_name="Wallet Password", required=False, advanced=True),
|
|
87
|
+
DictInput(
|
|
88
|
+
name="connection_params",
|
|
89
|
+
display_name="Additional Connection Parameters",
|
|
90
|
+
info="Non-secret Oracle options such as schema, config_dir, and wallet_location.",
|
|
91
|
+
required=False,
|
|
92
|
+
advanced=True,
|
|
93
|
+
),
|
|
94
|
+
MultilineInput(
|
|
95
|
+
name="metadata",
|
|
96
|
+
display_name="Metadata Columns",
|
|
97
|
+
required=False,
|
|
98
|
+
advanced=True,
|
|
99
|
+
info="Comma-separated list of result columns to copy into document metadata.",
|
|
100
|
+
),
|
|
101
|
+
DictInput(
|
|
102
|
+
name="parameter",
|
|
103
|
+
display_name="Bind Parameters",
|
|
104
|
+
required=False,
|
|
105
|
+
advanced=True,
|
|
106
|
+
info="Bind variables for the SQL query.",
|
|
107
|
+
),
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
outputs = [
|
|
111
|
+
Output(name="data", display_name="JSON", method="load_documents"),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def build_template_config(self) -> dict[str, Any]:
|
|
115
|
+
"""Build template config from this class to avoid cross-class metadata bleed."""
|
|
116
|
+
return self.get_template_config(self)
|
|
117
|
+
|
|
118
|
+
def _parse_metadata(self) -> list[str] | None:
|
|
119
|
+
metadata = getattr(self, "metadata", None)
|
|
120
|
+
if metadata is None:
|
|
121
|
+
return None
|
|
122
|
+
if isinstance(metadata, str):
|
|
123
|
+
values = [column.strip() for column in metadata.split(",") if column.strip()]
|
|
124
|
+
return values or None
|
|
125
|
+
if isinstance(metadata, list):
|
|
126
|
+
return metadata or None
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
def _parse_connection_params(self) -> dict[str, Any]:
|
|
130
|
+
connection_params = build_connection_params(
|
|
131
|
+
self.connection_params,
|
|
132
|
+
user=self.user,
|
|
133
|
+
password=self.password,
|
|
134
|
+
dsn=self.dsn,
|
|
135
|
+
wallet_password=self.wallet_password,
|
|
136
|
+
)
|
|
137
|
+
dsn = connection_params.get("dsn") or connection_params.get("connection_string") or connection_params.get("tns")
|
|
138
|
+
|
|
139
|
+
user = connection_params.get("user") or connection_params.get("username")
|
|
140
|
+
password = connection_params.get("password")
|
|
141
|
+
|
|
142
|
+
if isinstance(dsn, str) and (not user or not password):
|
|
143
|
+
parsed_user, parsed_password, parsed_dsn = split_credentialized_dsn(dsn)
|
|
144
|
+
user = user or parsed_user
|
|
145
|
+
password = password or parsed_password
|
|
146
|
+
dsn = parsed_dsn
|
|
147
|
+
|
|
148
|
+
if not user or not password:
|
|
149
|
+
msg = (
|
|
150
|
+
"Connection settings must include user and password, either in the dedicated fields or inside the DSN."
|
|
151
|
+
)
|
|
152
|
+
raise ValueError(msg)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"user": user,
|
|
156
|
+
"password": password,
|
|
157
|
+
"dsn": dsn,
|
|
158
|
+
"schema": connection_params.get("schema"),
|
|
159
|
+
"config_dir": connection_params.get("config_dir"),
|
|
160
|
+
"wallet_location": connection_params.get("wallet_location"),
|
|
161
|
+
"wallet_password": connection_params.get("wallet_password"),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
def _parse_parameter(self) -> Any:
|
|
165
|
+
parameter = getattr(self, "parameter", None)
|
|
166
|
+
return parameter or None
|
|
167
|
+
|
|
168
|
+
def load_documents(self) -> list[Data]:
|
|
169
|
+
loader = OracleAutonomousDatabaseLoader(
|
|
170
|
+
query=self.query,
|
|
171
|
+
metadata=self._parse_metadata(),
|
|
172
|
+
parameter=self._parse_parameter(),
|
|
173
|
+
**self._parse_connection_params(),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
data = [Data.from_document(doc) for doc in loader.load()]
|
|
177
|
+
self.status = data
|
|
178
|
+
return data
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import oracledb
|
|
9
|
+
from langchain_community.vectorstores.utils import DistanceStrategy
|
|
10
|
+
from langchain_oracledb.vectorstores import OracleVS
|
|
11
|
+
from langchain_oracledb.vectorstores.oraclevs import create_index
|
|
12
|
+
from lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
|
|
13
|
+
from lfx.helpers.data import docs_to_data
|
|
14
|
+
from lfx.io import BoolInput, DictInput, DropdownInput, HandleInput, IntInput, Output, SecretStrInput, StrInput
|
|
15
|
+
from lfx.schema.data import Data
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
from .connection import build_connection_params
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def serialize(obj: Any) -> dict | list:
|
|
22
|
+
if isinstance(obj, BaseModel):
|
|
23
|
+
return obj.model_dump(mode="json")
|
|
24
|
+
if isinstance(obj, dict):
|
|
25
|
+
return {k: serialize(v) for k, v in obj.items()}
|
|
26
|
+
if isinstance(obj, list):
|
|
27
|
+
return [serialize(item) for item in obj]
|
|
28
|
+
if isinstance(obj, tuple):
|
|
29
|
+
return [serialize(item) for item in obj]
|
|
30
|
+
return obj
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def normalize_search_result(obj: Any) -> Any:
|
|
34
|
+
if isinstance(obj, Decimal):
|
|
35
|
+
return float(obj)
|
|
36
|
+
if isinstance(obj, dict):
|
|
37
|
+
return {key: normalize_search_result(value) for key, value in obj.items()}
|
|
38
|
+
if isinstance(obj, list):
|
|
39
|
+
return [normalize_search_result(item) for item in obj]
|
|
40
|
+
if isinstance(obj, tuple):
|
|
41
|
+
return [normalize_search_result(item) for item in obj]
|
|
42
|
+
return obj
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OracleVectorStoreComponent(LCVectorStoreComponent):
|
|
46
|
+
display_name = "Oracle Vector Store"
|
|
47
|
+
description = "Oracle vector store with search capabilities"
|
|
48
|
+
name = "OracleVS"
|
|
49
|
+
icon = "Oracle"
|
|
50
|
+
|
|
51
|
+
inputs = [
|
|
52
|
+
SecretStrInput(name="user", display_name="User", required=False),
|
|
53
|
+
SecretStrInput(name="password", display_name="Password", required=False),
|
|
54
|
+
SecretStrInput(name="dsn", display_name="DSN", required=True),
|
|
55
|
+
SecretStrInput(name="wallet_password", display_name="Wallet Password", required=False, advanced=True),
|
|
56
|
+
DictInput(
|
|
57
|
+
name="connection_params",
|
|
58
|
+
display_name="Additional Connection Parameters",
|
|
59
|
+
info="Non-secret arguments passed to python-oracledb connect(), such as config_dir and wallet_location.",
|
|
60
|
+
list=True,
|
|
61
|
+
required=False,
|
|
62
|
+
advanced=True,
|
|
63
|
+
),
|
|
64
|
+
StrInput(name="table_name", display_name="Table Name", required=True),
|
|
65
|
+
*LCVectorStoreComponent.inputs,
|
|
66
|
+
HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"], required=True),
|
|
67
|
+
IntInput(
|
|
68
|
+
name="number_of_results",
|
|
69
|
+
display_name="Number of Results",
|
|
70
|
+
info="Number of results to return.",
|
|
71
|
+
value=4,
|
|
72
|
+
advanced=True,
|
|
73
|
+
),
|
|
74
|
+
DropdownInput(
|
|
75
|
+
name="search_type",
|
|
76
|
+
display_name="Search Type",
|
|
77
|
+
info="Search type to use",
|
|
78
|
+
options=["Similarity", "MMR (Max Marginal Relevance)"],
|
|
79
|
+
value="Similarity",
|
|
80
|
+
advanced=True,
|
|
81
|
+
),
|
|
82
|
+
DropdownInput(
|
|
83
|
+
name="distance_strategy",
|
|
84
|
+
display_name="Distance Strategy",
|
|
85
|
+
options=["EUCLIDEAN", "DOT", "COSINE"],
|
|
86
|
+
value="COSINE",
|
|
87
|
+
advanced=True,
|
|
88
|
+
),
|
|
89
|
+
BoolInput(
|
|
90
|
+
name="create_index",
|
|
91
|
+
display_name="Create Vector Index",
|
|
92
|
+
info=(
|
|
93
|
+
"Boolean flag to determine whether to create a vector index. "
|
|
94
|
+
"Check `Controls` to use advanced parameters."
|
|
95
|
+
),
|
|
96
|
+
value=True,
|
|
97
|
+
),
|
|
98
|
+
DictInput(
|
|
99
|
+
name="index_params",
|
|
100
|
+
display_name="Vector Index Parameters",
|
|
101
|
+
list=True,
|
|
102
|
+
advanced=True,
|
|
103
|
+
),
|
|
104
|
+
BoolInput(
|
|
105
|
+
name="mutate_on_duplicate",
|
|
106
|
+
display_name="Mutate on Duplicate Inserts",
|
|
107
|
+
info="Whether to update existing rows when duplicate inserts are encountered.",
|
|
108
|
+
value=False,
|
|
109
|
+
advanced=True,
|
|
110
|
+
),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
# Mirrors LCVectorStoreComponent.outputs verbatim: the extension
|
|
114
|
+
# validator statically scans this file and cannot see inherited
|
|
115
|
+
# outputs, so the declaration must be repeated here.
|
|
116
|
+
outputs = [
|
|
117
|
+
Output(
|
|
118
|
+
display_name="Search Results",
|
|
119
|
+
name="search_results",
|
|
120
|
+
method="search_documents",
|
|
121
|
+
info=(
|
|
122
|
+
"Performs semantic similarity search in the vector store to find relevant documents/files. "
|
|
123
|
+
"Input: A natural language search query (str). "
|
|
124
|
+
"Returns: A list of Data objects (dicts). Each result has structure: {'data': {'text': '...', ...}}. "
|
|
125
|
+
"The 'text' field always contains the matched document content. "
|
|
126
|
+
"Other metadata fields depend on what was indexed "
|
|
127
|
+
"(may include: 'file_path', 'source', 'page', etc.). "
|
|
128
|
+
"IMPORTANT: To extract metadata like file_path, use: "
|
|
129
|
+
"result['data'].get('file_path') or result.data.get('file_path'). "
|
|
130
|
+
"Check what fields are available by inspecting result['data'].keys() or the first search result."
|
|
131
|
+
),
|
|
132
|
+
tool_mode=True,
|
|
133
|
+
),
|
|
134
|
+
Output(
|
|
135
|
+
display_name="Table",
|
|
136
|
+
name="dataframe",
|
|
137
|
+
method="as_dataframe",
|
|
138
|
+
),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
def build_template_config(self) -> dict[str, Any]:
|
|
142
|
+
return self.get_template_config(self)
|
|
143
|
+
|
|
144
|
+
@check_cached_vector_store
|
|
145
|
+
def build_vector_store(self) -> OracleVS:
|
|
146
|
+
# Convert DataFrame to Data if needed using parent's method
|
|
147
|
+
self.ingest_data = self._prepare_ingest_data()
|
|
148
|
+
|
|
149
|
+
documents = []
|
|
150
|
+
for _input in self.ingest_data or []:
|
|
151
|
+
inp = _input
|
|
152
|
+
if isinstance(_input, Data):
|
|
153
|
+
inp = _input.to_lc_document()
|
|
154
|
+
|
|
155
|
+
if hasattr(inp, "metadata"):
|
|
156
|
+
inp.metadata = serialize(inp.metadata)
|
|
157
|
+
|
|
158
|
+
documents.append(inp)
|
|
159
|
+
|
|
160
|
+
opened_connection = False
|
|
161
|
+
if (not hasattr(self, "connection")) or (not self.connection):
|
|
162
|
+
connection_params = build_connection_params(
|
|
163
|
+
self.connection_params,
|
|
164
|
+
user=self.user,
|
|
165
|
+
password=self.password,
|
|
166
|
+
dsn=self.dsn,
|
|
167
|
+
wallet_password=self.wallet_password,
|
|
168
|
+
)
|
|
169
|
+
self.connection = oracledb.connect(**connection_params)
|
|
170
|
+
opened_connection = True
|
|
171
|
+
|
|
172
|
+
distance_strategy2function = {
|
|
173
|
+
"EUCLIDEAN": DistanceStrategy.EUCLIDEAN_DISTANCE,
|
|
174
|
+
"DOT": DistanceStrategy.DOT_PRODUCT,
|
|
175
|
+
"COSINE": DistanceStrategy.COSINE,
|
|
176
|
+
}
|
|
177
|
+
distance_strategy = distance_strategy2function[self.distance_strategy]
|
|
178
|
+
|
|
179
|
+
params = {
|
|
180
|
+
"client": self.connection,
|
|
181
|
+
"table_name": self.table_name,
|
|
182
|
+
"distance_strategy": distance_strategy,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if not version("langchain_oracledb").startswith("1.0"):
|
|
186
|
+
params["mutate_on_duplicate"] = self.mutate_on_duplicate
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
if documents:
|
|
190
|
+
vs = OracleVS.from_documents(embedding=self.embedding, documents=documents, **params)
|
|
191
|
+
else:
|
|
192
|
+
vs = OracleVS(embedding_function=self.embedding, **params)
|
|
193
|
+
|
|
194
|
+
if self.create_index:
|
|
195
|
+
index_params = (self.index_params or {}).copy()
|
|
196
|
+
|
|
197
|
+
if "idx_name" not in index_params:
|
|
198
|
+
params_str = json.dumps(index_params, sort_keys=True)
|
|
199
|
+
distance_strategy = self.distance_strategy
|
|
200
|
+
params_str = params_str + distance_strategy
|
|
201
|
+
index_name = params["table_name"] + "_idx_" + hashlib.sha256(params_str.encode()).hexdigest()[:16]
|
|
202
|
+
index_params["idx_name"] = index_name
|
|
203
|
+
|
|
204
|
+
if "idx_type" not in index_params:
|
|
205
|
+
index_params["idx_type"] = "HNSW"
|
|
206
|
+
|
|
207
|
+
if "" in index_params:
|
|
208
|
+
del index_params[""]
|
|
209
|
+
create_index(self.connection, vs, index_params)
|
|
210
|
+
except Exception:
|
|
211
|
+
if opened_connection:
|
|
212
|
+
with suppress(Exception):
|
|
213
|
+
self.connection.close()
|
|
214
|
+
self.connection = None
|
|
215
|
+
raise
|
|
216
|
+
|
|
217
|
+
return vs
|
|
218
|
+
|
|
219
|
+
def _map_search_type(self) -> str:
|
|
220
|
+
if self.search_type == "MMR (Max Marginal Relevance)":
|
|
221
|
+
return "mmr"
|
|
222
|
+
return "similarity"
|
|
223
|
+
|
|
224
|
+
def _build_search_args(self) -> dict[str, Any]:
|
|
225
|
+
return {"k": self.number_of_results}
|
|
226
|
+
|
|
227
|
+
def search_documents(self) -> list[Data]:
|
|
228
|
+
raw_search_query = getattr(self, "search_query", None)
|
|
229
|
+
search_query = str(raw_search_query).strip() if raw_search_query is not None else ""
|
|
230
|
+
|
|
231
|
+
if not search_query:
|
|
232
|
+
self.status = []
|
|
233
|
+
return []
|
|
234
|
+
|
|
235
|
+
vector_store = self.build_vector_store()
|
|
236
|
+
docs = vector_store.search(
|
|
237
|
+
query=search_query,
|
|
238
|
+
search_type=self._map_search_type(),
|
|
239
|
+
**self._build_search_args(),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
data = docs_to_data(docs)
|
|
243
|
+
for item in data:
|
|
244
|
+
item.data = normalize_search_result(item.data)
|
|
245
|
+
self.status = data
|
|
246
|
+
return data
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.langflow.org/extension/v1.json",
|
|
3
|
+
"id": "lfx-oracle",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"name": "Oracle",
|
|
6
|
+
"description": "Oracle component(s) as a standalone Langflow Extension Bundle.",
|
|
7
|
+
"lfx": {
|
|
8
|
+
"compat": ["1"]
|
|
9
|
+
},
|
|
10
|
+
"bundles": [
|
|
11
|
+
{
|
|
12
|
+
"name": "oracle",
|
|
13
|
+
"path": "components/oracle"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lfx-oracle
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Oracle Database components (Doc Loader, Autonomous Database Loader, Embeddings, Vector Store) 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,oracle,oracledb,vector-store
|
|
11
|
+
Requires-Python: <3.15,>=3.10
|
|
12
|
+
Requires-Dist: langchain-community<1.0.0,>=0.4.1
|
|
13
|
+
Requires-Dist: langchain-oracledb<2.0.0,>=1.0.1
|
|
14
|
+
Requires-Dist: lfx<2.0.0,>=1.11.0.dev0
|
|
15
|
+
Requires-Dist: oracledb<4.0.0,>=2.2.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# lfx-oracle
|
|
19
|
+
|
|
20
|
+
Oracle Database components (Doc Loader, Autonomous Database Loader,
|
|
21
|
+
Embeddings, Vector Store) as a standalone Langflow Extension Bundle.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install lfx-oracle
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The bundle is registered automatically via the `langflow.extensions`
|
|
30
|
+
entry-point. After install, restart your Langflow server; the bundle's
|
|
31
|
+
components will appear in the palette under the `Oracle` group.
|
|
32
|
+
|
|
33
|
+
## Develop
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd src/bundles/oracle
|
|
37
|
+
pip install -e .
|
|
38
|
+
lfx extension validate src/lfx_oracle
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Migration
|
|
42
|
+
|
|
43
|
+
Saved flows referencing the legacy class name(s) or the old import paths
|
|
44
|
+
under `lfx.components.oracledb.*` are rewritten to the new namespaced
|
|
45
|
+
IDs by the migration table in
|
|
46
|
+
`src/lfx/src/lfx/extension/migration/migration_table.json`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
lfx_oracle/__init__.py,sha256=YFjqnOfX-NwVbqX_sUbODKfHtfaGpOf-In3RrmSs2lg,735
|
|
2
|
+
lfx_oracle/extension.json,sha256=mKatK928YRWMnI7FpxlIR6Etjo56WJUvhmXLMUb29c8,339
|
|
3
|
+
lfx_oracle/components/oracle/__init__.py,sha256=j6jLNObRB-mfQlnemFPWN81sZ1r-xjz1e0onvpnPWrA,632
|
|
4
|
+
lfx_oracle/components/oracle/connection.py,sha256=6zFIFTsvsnUUVkfKwMytlZ9eYYdcGN2etPxZ9-eUu74,2377
|
|
5
|
+
lfx_oracle/components/oracle/oracledb_embeddings.py,sha256=SIY1OjOMYXR_sX7X9A5S4d416qziecDyC47GtKdxqgs,2562
|
|
6
|
+
lfx_oracle/components/oracle/oracledb_loaders.py,sha256=GRCPuuO1b9MxoF6V-1fKAuxbBokRxbBZ9hM0si8D2Hw,6742
|
|
7
|
+
lfx_oracle/components/oracle/oraclevs.py,sha256=MxBd9hWOy4OFupLyrADiXUbXJEZfEhcZxKueQsJDss8,9258
|
|
8
|
+
lfx_oracle-0.1.0.dist-info/METADATA,sha256=15gA-JmbYUZjRUFII4194aWwXOAD9hZ7Le9bG40wIBk,1521
|
|
9
|
+
lfx_oracle-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
10
|
+
lfx_oracle-0.1.0.dist-info/entry_points.txt,sha256=BJ7HwFhchI9erTGgLTs--q9cHK2xQVBgQLL3g5DXbZA,46
|
|
11
|
+
lfx_oracle-0.1.0.dist-info/RECORD,,
|