mirror of
https://github.com/lancedb/lancedb.git
synced 2025-12-23 13:29:57 +00:00
Compare commits
101 Commits
python-v0.
...
python-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3b45a4d00 | ||
|
|
c316c2f532 | ||
|
|
3966b16b63 | ||
|
|
5661cc15ac | ||
|
|
4e7220400f | ||
|
|
ae4928fe77 | ||
|
|
e80a405dee | ||
|
|
a53e19e386 | ||
|
|
c0097c5f0a | ||
|
|
c199708e64 | ||
|
|
4a47150ae7 | ||
|
|
f86b20a564 | ||
|
|
cc81f3e1a5 | ||
|
|
bc49c4db82 | ||
|
|
d2eec46f17 | ||
|
|
51437bc228 | ||
|
|
fa53cfcfd2 | ||
|
|
374fe0ad95 | ||
|
|
35e5b84ba9 | ||
|
|
7c12d497b0 | ||
|
|
dfe4ba8dad | ||
|
|
fa1b9ad5bd | ||
|
|
8877eb020d | ||
|
|
01e4291d21 | ||
|
|
ab3ea76ad1 | ||
|
|
728ef8657d | ||
|
|
0b13901a16 | ||
|
|
84b110e0ef | ||
|
|
e1836e54e3 | ||
|
|
4ba5326880 | ||
|
|
b036a69300 | ||
|
|
5b12a47119 | ||
|
|
769d483e50 | ||
|
|
9ecb11fe5a | ||
|
|
22bd8329f3 | ||
|
|
a736fad149 | ||
|
|
072adc41aa | ||
|
|
c6f25ef1f0 | ||
|
|
2f0c5baea2 | ||
|
|
a63dd66d41 | ||
|
|
d6b3ccb37b | ||
|
|
c4f99e82e5 | ||
|
|
979a2d3d9d | ||
|
|
7ac5f74c80 | ||
|
|
ecdee4d2b1 | ||
|
|
f391ed828a | ||
|
|
a99a450f2b | ||
|
|
6fa1f37506 | ||
|
|
544382df5e | ||
|
|
784f00ef6d | ||
|
|
96d7446f70 | ||
|
|
99ea78fb55 | ||
|
|
8eef4cdc28 | ||
|
|
0f102f02c3 | ||
|
|
a33a0670f6 | ||
|
|
14c9ff46d1 | ||
|
|
1865f7decf | ||
|
|
a608621476 | ||
|
|
00514999ff | ||
|
|
b3b597fef6 | ||
|
|
bf17144591 | ||
|
|
09e110525f | ||
|
|
40f0dbb64d | ||
|
|
3b19e96ae7 | ||
|
|
78a17ad54c | ||
|
|
a8e6b491e2 | ||
|
|
cea541ca46 | ||
|
|
873ffc1042 | ||
|
|
83273ad997 | ||
|
|
d18d63c69d | ||
|
|
c3e865e8d0 | ||
|
|
a7755cb313 | ||
|
|
3490f3456f | ||
|
|
0a1d0693e1 | ||
|
|
fd330b4b4b | ||
|
|
d4e9fc08e0 | ||
|
|
3626f2f5e1 | ||
|
|
e64712cfa5 | ||
|
|
3e3118f85c | ||
|
|
592598a333 | ||
|
|
5ad21341c9 | ||
|
|
6e08caa091 | ||
|
|
7e259d8b0f | ||
|
|
e84f747464 | ||
|
|
998cd43fe6 | ||
|
|
4bc7eebe61 | ||
|
|
2e3b34e79b | ||
|
|
e7574698eb | ||
|
|
801a9e5f6f | ||
|
|
4e5fbe6c99 | ||
|
|
1a449fa49e | ||
|
|
6bf742c759 | ||
|
|
ef3093bc23 | ||
|
|
16851389ea | ||
|
|
c269524b2f | ||
|
|
f6eef14313 | ||
|
|
32716adaa3 | ||
|
|
5e98b7f4c0 | ||
|
|
3f2589c11f | ||
|
|
e3b99694d6 | ||
|
|
9d42dc349c |
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.15.1-beta.2"
|
current_version = "0.18.0"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
48
.github/workflows/python.yml
vendored
48
.github/workflows/python.yml
vendored
@@ -33,13 +33,14 @@ jobs:
|
|||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- name: Install ruff
|
- name: Install ruff
|
||||||
run: |
|
run: |
|
||||||
pip install ruff==0.8.4
|
pip install ruff==0.9.9
|
||||||
- name: Format check
|
- name: Format check
|
||||||
run: ruff format --check .
|
run: ruff format --check .
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: ruff check .
|
run: ruff check .
|
||||||
doctest:
|
|
||||||
name: "Doctest"
|
type-check:
|
||||||
|
name: "Type Check"
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: "ubuntu-22.04"
|
runs-on: "ubuntu-22.04"
|
||||||
defaults:
|
defaults:
|
||||||
@@ -54,7 +55,36 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.12"
|
||||||
|
- name: Install protobuf compiler
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y protobuf-compiler
|
||||||
|
pip install toml
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python ../ci/parse_requirements.py pyproject.toml --extras dev,tests,embeddings > requirements.txt
|
||||||
|
pip install -r requirements.txt
|
||||||
|
- name: Run pyright
|
||||||
|
run: pyright
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
name: "Doctest"
|
||||||
|
timeout-minutes: 30
|
||||||
|
runs-on: "ubuntu-24.04"
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: python
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
- name: Install protobuf
|
- name: Install protobuf
|
||||||
run: |
|
run: |
|
||||||
@@ -75,8 +105,8 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-minor-version: ["9", "11"]
|
python-minor-version: ["9", "12"]
|
||||||
runs-on: "ubuntu-22.04"
|
runs-on: "ubuntu-24.04"
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -127,7 +157,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.12"
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: python
|
workspaces: python
|
||||||
@@ -157,7 +187,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.12"
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: python
|
workspaces: python
|
||||||
@@ -168,7 +198,7 @@ jobs:
|
|||||||
run: rm -rf target/wheels
|
run: rm -rf target/wheels
|
||||||
pydantic1x:
|
pydantic1x:
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: "ubuntu-22.04"
|
runs-on: "ubuntu-24.04"
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
23
.github/workflows/rust.yml
vendored
23
.github/workflows/rust.yml
vendored
@@ -61,7 +61,12 @@ jobs:
|
|||||||
CXX: clang++
|
CXX: clang++
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# Remote cargo.lock to force a fresh build
|
# Building without a lock file often requires the latest Rust version since downstream
|
||||||
|
# dependencies may have updated their minimum Rust version.
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "stable"
|
||||||
|
# Remove cargo.lock to force a fresh build
|
||||||
- name: Remove Cargo.lock
|
- name: Remove Cargo.lock
|
||||||
run: rm -f Cargo.lock
|
run: rm -f Cargo.lock
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
@@ -179,15 +184,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies (part 1)
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
apk add protobuf-dev curl clang lld llvm19 grep npm bash msitools sed
|
apk add protobuf-dev curl clang lld llvm19 grep npm bash msitools sed
|
||||||
|
- name: Install rust
|
||||||
curl --proto '=https' --tlsv1.3 -sSf https://raw.githubusercontent.com/rust-lang/rustup/refs/heads/master/rustup-init.sh | sh -s -- -y
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
source $HOME/.cargo/env
|
with:
|
||||||
rustup target add aarch64-pc-windows-msvc
|
target: aarch64-pc-windows-msvc
|
||||||
|
- name: Install dependencies (part 2)
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
mkdir -p sysroot
|
mkdir -p sysroot
|
||||||
cd sysroot
|
cd sysroot
|
||||||
sh ../ci/sysroot-aarch64-pc-windows-msvc.sh
|
sh ../ci/sysroot-aarch64-pc-windows-msvc.sh
|
||||||
@@ -259,7 +266,7 @@ jobs:
|
|||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
run: |
|
run: |
|
||||||
Invoke-WebRequest https://win.rustup.rs/x86_64 -OutFile rustup-init.exe
|
Invoke-WebRequest https://win.rustup.rs/x86_64 -OutFile rustup-init.exe
|
||||||
.\rustup-init.exe -y --default-host aarch64-pc-windows-msvc
|
.\rustup-init.exe -y --default-host aarch64-pc-windows-msvc --default-toolchain 1.83.0
|
||||||
shell: powershell
|
shell: powershell
|
||||||
- name: Add Rust to PATH
|
- name: Add Rust to PATH
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v3.2.0
|
rev: v3.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.2.2
|
rev: v0.9.9
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
- repo: local
|
# - repo: https://github.com/RobertCraigie/pyright-python
|
||||||
hooks:
|
# rev: v1.1.395
|
||||||
- id: local-biome-check
|
# hooks:
|
||||||
name: biome check
|
# - id: pyright
|
||||||
entry: npx @biomejs/biome@1.8.3 check --config-path nodejs/biome.json nodejs/
|
# args: ["--project", "python"]
|
||||||
language: system
|
# additional_dependencies: [pyarrow-stubs]
|
||||||
types: [text]
|
- repo: local
|
||||||
files: "nodejs/.*"
|
hooks:
|
||||||
exclude: nodejs/lancedb/native.d.ts|nodejs/dist/.*|nodejs/examples/.*
|
- id: local-biome-check
|
||||||
|
name: biome check
|
||||||
|
entry: npx @biomejs/biome@1.8.3 check --config-path nodejs/biome.json nodejs/
|
||||||
|
language: system
|
||||||
|
types: [text]
|
||||||
|
files: "nodejs/.*"
|
||||||
|
exclude: nodejs/lancedb/native.d.ts|nodejs/dist/.*|nodejs/examples/.*
|
||||||
|
|||||||
1345
Cargo.lock
generated
1345
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
58
Cargo.toml
58
Cargo.toml
@@ -21,44 +21,52 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.78.0"
|
rust-version = "1.78.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=0.23.0", "features" = [
|
lance = { "version" = "=0.24.1", "features" = ["dynamodb"] }
|
||||||
"dynamodb",
|
lance-io = { version = "=0.24.1" }
|
||||||
], git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
lance-index = { version = "=0.24.1" }
|
||||||
lance-io = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
lance-linalg = { version = "=0.24.1" }
|
||||||
lance-index = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
lance-table = { version = "=0.24.1" }
|
||||||
lance-linalg = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
lance-testing = { version = "=0.24.1" }
|
||||||
lance-table = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
lance-datafusion = { version = "=0.24.1" }
|
||||||
lance-testing = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
lance-encoding = { version = "=0.24.1" }
|
||||||
lance-datafusion = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
|
||||||
lance-encoding = { version = "=0.23.0", git = "https://github.com/lancedb/lance.git", tag = "v0.23.0-beta.4" }
|
|
||||||
# Note that this one does not include pyarrow
|
# Note that this one does not include pyarrow
|
||||||
arrow = { version = "53.2", optional = false }
|
arrow = { version = "54.1", optional = false }
|
||||||
arrow-array = "53.2"
|
arrow-array = "54.1"
|
||||||
arrow-data = "53.2"
|
arrow-data = "54.1"
|
||||||
arrow-ipc = "53.2"
|
arrow-ipc = "54.1"
|
||||||
arrow-ord = "53.2"
|
arrow-ord = "54.1"
|
||||||
arrow-schema = "53.2"
|
arrow-schema = "54.1"
|
||||||
arrow-arith = "53.2"
|
arrow-arith = "54.1"
|
||||||
arrow-cast = "53.2"
|
arrow-cast = "54.1"
|
||||||
async-trait = "0"
|
async-trait = "0"
|
||||||
chrono = "0.4.35"
|
datafusion = { version = "45.0", default-features = false }
|
||||||
datafusion-common = "44.0"
|
datafusion-catalog = "45.0"
|
||||||
datafusion-physical-plan = "44.0"
|
datafusion-common = { version = "45.0", default-features = false }
|
||||||
env_logger = "0.10"
|
datafusion-execution = "45.0"
|
||||||
|
datafusion-expr = "45.0"
|
||||||
|
datafusion-physical-plan = "45.0"
|
||||||
|
env_logger = "0.11"
|
||||||
half = { "version" = "=2.4.1", default-features = false, features = [
|
half = { "version" = "=2.4.1", default-features = false, features = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
] }
|
] }
|
||||||
futures = "0"
|
futures = "0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
moka = { version = "0.11", features = ["future"] }
|
moka = { version = "0.12", features = ["future"] }
|
||||||
object_store = "0.10.2"
|
object_store = "0.11.0"
|
||||||
pin-project = "1.0.7"
|
pin-project = "1.0.7"
|
||||||
snafu = "0.7.4"
|
snafu = "0.8"
|
||||||
url = "2"
|
url = "2"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
semver = "1.0.25"
|
||||||
|
|
||||||
|
# Temporary pins to work around downstream issues
|
||||||
|
# https://github.com/apache/arrow-rs/commit/2fddf85afcd20110ce783ed5b4cdeb82293da30b
|
||||||
|
chrono = "=0.4.39"
|
||||||
|
# https://github.com/RustCrypto/formats/issues/1684
|
||||||
|
base64ct = "=1.6.0"
|
||||||
|
|
||||||
# Workaround for: https://github.com/eira-fransham/crunchy/issues/13
|
# Workaround for: https://github.com/eira-fransham/crunchy/issues/13
|
||||||
crunchy = "=0.2.2"
|
crunchy = "=0.2.2"
|
||||||
|
|||||||
41
ci/parse_requirements.py
Normal file
41
ci/parse_requirements.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import argparse
|
||||||
|
import toml
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dependencies(pyproject_path, extras=None):
|
||||||
|
with open(pyproject_path, "r") as file:
|
||||||
|
pyproject = toml.load(file)
|
||||||
|
|
||||||
|
dependencies = pyproject.get("project", {}).get("dependencies", [])
|
||||||
|
for dependency in dependencies:
|
||||||
|
print(dependency)
|
||||||
|
|
||||||
|
optional_dependencies = pyproject.get("project", {}).get(
|
||||||
|
"optional-dependencies", {}
|
||||||
|
)
|
||||||
|
|
||||||
|
if extras:
|
||||||
|
for extra in extras.split(","):
|
||||||
|
for dep in optional_dependencies.get(extra, []):
|
||||||
|
print(dep)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate requirements.txt from pyproject.toml"
|
||||||
|
)
|
||||||
|
parser.add_argument("path", type=str, help="Path to pyproject.toml")
|
||||||
|
parser.add_argument(
|
||||||
|
"--extras",
|
||||||
|
type=str,
|
||||||
|
help="Comma-separated list of extras to include",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
parse_dependencies(args.path, args.extras)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -4,6 +4,9 @@ repo_url: https://github.com/lancedb/lancedb
|
|||||||
edit_uri: https://github.com/lancedb/lancedb/tree/main/docs/src
|
edit_uri: https://github.com/lancedb/lancedb/tree/main/docs/src
|
||||||
repo_name: lancedb/lancedb
|
repo_name: lancedb/lancedb
|
||||||
docs_dir: src
|
docs_dir: src
|
||||||
|
watch:
|
||||||
|
- src
|
||||||
|
- ../python/python
|
||||||
|
|
||||||
theme:
|
theme:
|
||||||
name: "material"
|
name: "material"
|
||||||
@@ -63,6 +66,7 @@ plugins:
|
|||||||
- https://arrow.apache.org/docs/objects.inv
|
- https://arrow.apache.org/docs/objects.inv
|
||||||
- https://pandas.pydata.org/docs/objects.inv
|
- https://pandas.pydata.org/docs/objects.inv
|
||||||
- https://lancedb.github.io/lance/objects.inv
|
- https://lancedb.github.io/lance/objects.inv
|
||||||
|
- https://docs.pydantic.dev/latest/objects.inv
|
||||||
- mkdocs-jupyter
|
- mkdocs-jupyter
|
||||||
- render_swagger:
|
- render_swagger:
|
||||||
allow_arbitrary_locations: true
|
allow_arbitrary_locations: true
|
||||||
@@ -105,8 +109,8 @@ nav:
|
|||||||
- 📚 Concepts:
|
- 📚 Concepts:
|
||||||
- Vector search: concepts/vector_search.md
|
- Vector search: concepts/vector_search.md
|
||||||
- Indexing:
|
- Indexing:
|
||||||
- IVFPQ: concepts/index_ivfpq.md
|
- IVFPQ: concepts/index_ivfpq.md
|
||||||
- HNSW: concepts/index_hnsw.md
|
- HNSW: concepts/index_hnsw.md
|
||||||
- Storage: concepts/storage.md
|
- Storage: concepts/storage.md
|
||||||
- Data management: concepts/data_management.md
|
- Data management: concepts/data_management.md
|
||||||
- 🔨 Guides:
|
- 🔨 Guides:
|
||||||
@@ -130,8 +134,8 @@ nav:
|
|||||||
- Adaptive RAG: rag/adaptive_rag.md
|
- Adaptive RAG: rag/adaptive_rag.md
|
||||||
- SFR RAG: rag/sfr_rag.md
|
- SFR RAG: rag/sfr_rag.md
|
||||||
- Advanced Techniques:
|
- Advanced Techniques:
|
||||||
- HyDE: rag/advanced_techniques/hyde.md
|
- HyDE: rag/advanced_techniques/hyde.md
|
||||||
- FLARE: rag/advanced_techniques/flare.md
|
- FLARE: rag/advanced_techniques/flare.md
|
||||||
- Reranking:
|
- Reranking:
|
||||||
- Quickstart: reranking/index.md
|
- Quickstart: reranking/index.md
|
||||||
- Cohere Reranker: reranking/cohere.md
|
- Cohere Reranker: reranking/cohere.md
|
||||||
@@ -146,7 +150,7 @@ nav:
|
|||||||
- Building Custom Rerankers: reranking/custom_reranker.md
|
- Building Custom Rerankers: reranking/custom_reranker.md
|
||||||
- Example: notebooks/lancedb_reranking.ipynb
|
- Example: notebooks/lancedb_reranking.ipynb
|
||||||
- Filtering: sql.md
|
- Filtering: sql.md
|
||||||
- Versioning & Reproducibility:
|
- Versioning & Reproducibility:
|
||||||
- sync API: notebooks/reproducibility.ipynb
|
- sync API: notebooks/reproducibility.ipynb
|
||||||
- async API: notebooks/reproducibility_async.ipynb
|
- async API: notebooks/reproducibility_async.ipynb
|
||||||
- Configuring Storage: guides/storage.md
|
- Configuring Storage: guides/storage.md
|
||||||
@@ -178,6 +182,7 @@ nav:
|
|||||||
- Imagebind embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/imagebind_embedding.md
|
- Imagebind embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/imagebind_embedding.md
|
||||||
- Jina Embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/jina_multimodal_embedding.md
|
- Jina Embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/jina_multimodal_embedding.md
|
||||||
- User-defined embedding functions: embeddings/custom_embedding_function.md
|
- User-defined embedding functions: embeddings/custom_embedding_function.md
|
||||||
|
- Variables and secrets: embeddings/variables_and_secrets.md
|
||||||
- "Example: Multi-lingual semantic search": notebooks/multi_lingual_example.ipynb
|
- "Example: Multi-lingual semantic search": notebooks/multi_lingual_example.ipynb
|
||||||
- "Example: MultiModal CLIP Embeddings": notebooks/DisappearingEmbeddingFunction.ipynb
|
- "Example: MultiModal CLIP Embeddings": notebooks/DisappearingEmbeddingFunction.ipynb
|
||||||
- 🔌 Integrations:
|
- 🔌 Integrations:
|
||||||
@@ -240,8 +245,8 @@ nav:
|
|||||||
- Concepts:
|
- Concepts:
|
||||||
- Vector search: concepts/vector_search.md
|
- Vector search: concepts/vector_search.md
|
||||||
- Indexing:
|
- Indexing:
|
||||||
- IVFPQ: concepts/index_ivfpq.md
|
- IVFPQ: concepts/index_ivfpq.md
|
||||||
- HNSW: concepts/index_hnsw.md
|
- HNSW: concepts/index_hnsw.md
|
||||||
- Storage: concepts/storage.md
|
- Storage: concepts/storage.md
|
||||||
- Data management: concepts/data_management.md
|
- Data management: concepts/data_management.md
|
||||||
- Guides:
|
- Guides:
|
||||||
@@ -265,8 +270,8 @@ nav:
|
|||||||
- Adaptive RAG: rag/adaptive_rag.md
|
- Adaptive RAG: rag/adaptive_rag.md
|
||||||
- SFR RAG: rag/sfr_rag.md
|
- SFR RAG: rag/sfr_rag.md
|
||||||
- Advanced Techniques:
|
- Advanced Techniques:
|
||||||
- HyDE: rag/advanced_techniques/hyde.md
|
- HyDE: rag/advanced_techniques/hyde.md
|
||||||
- FLARE: rag/advanced_techniques/flare.md
|
- FLARE: rag/advanced_techniques/flare.md
|
||||||
- Reranking:
|
- Reranking:
|
||||||
- Quickstart: reranking/index.md
|
- Quickstart: reranking/index.md
|
||||||
- Cohere Reranker: reranking/cohere.md
|
- Cohere Reranker: reranking/cohere.md
|
||||||
@@ -280,7 +285,7 @@ nav:
|
|||||||
- Building Custom Rerankers: reranking/custom_reranker.md
|
- Building Custom Rerankers: reranking/custom_reranker.md
|
||||||
- Example: notebooks/lancedb_reranking.ipynb
|
- Example: notebooks/lancedb_reranking.ipynb
|
||||||
- Filtering: sql.md
|
- Filtering: sql.md
|
||||||
- Versioning & Reproducibility:
|
- Versioning & Reproducibility:
|
||||||
- sync API: notebooks/reproducibility.ipynb
|
- sync API: notebooks/reproducibility.ipynb
|
||||||
- async API: notebooks/reproducibility_async.ipynb
|
- async API: notebooks/reproducibility_async.ipynb
|
||||||
- Configuring Storage: guides/storage.md
|
- Configuring Storage: guides/storage.md
|
||||||
@@ -311,6 +316,7 @@ nav:
|
|||||||
- Imagebind embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/imagebind_embedding.md
|
- Imagebind embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/imagebind_embedding.md
|
||||||
- Jina Embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/jina_multimodal_embedding.md
|
- Jina Embeddings: embeddings/available_embedding_models/multimodal_embedding_functions/jina_multimodal_embedding.md
|
||||||
- User-defined embedding functions: embeddings/custom_embedding_function.md
|
- User-defined embedding functions: embeddings/custom_embedding_function.md
|
||||||
|
- Variables and secrets: embeddings/variables_and_secrets.md
|
||||||
- "Example: Multi-lingual semantic search": notebooks/multi_lingual_example.ipynb
|
- "Example: Multi-lingual semantic search": notebooks/multi_lingual_example.ipynb
|
||||||
- "Example: MultiModal CLIP Embeddings": notebooks/DisappearingEmbeddingFunction.ipynb
|
- "Example: MultiModal CLIP Embeddings": notebooks/DisappearingEmbeddingFunction.ipynb
|
||||||
- Integrations:
|
- Integrations:
|
||||||
@@ -349,8 +355,8 @@ nav:
|
|||||||
- 🦀 Rust:
|
- 🦀 Rust:
|
||||||
- Overview: examples/examples_rust.md
|
- Overview: examples/examples_rust.md
|
||||||
- Studies:
|
- Studies:
|
||||||
- studies/overview.md
|
- studies/overview.md
|
||||||
- ↗Improve retrievers with hybrid search and reranking: https://blog.lancedb.com/hybrid-search-and-reranking-report/
|
- ↗Improve retrievers with hybrid search and reranking: https://blog.lancedb.com/hybrid-search-and-reranking-report/
|
||||||
- API reference:
|
- API reference:
|
||||||
- Overview: api_reference.md
|
- Overview: api_reference.md
|
||||||
- Python: python/python.md
|
- Python: python/python.md
|
||||||
@@ -371,6 +377,7 @@ extra_css:
|
|||||||
|
|
||||||
extra_javascript:
|
extra_javascript:
|
||||||
- "extra_js/init_ask_ai_widget.js"
|
- "extra_js/init_ask_ai_widget.js"
|
||||||
|
- "extra_js/reo.js"
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
analytics:
|
analytics:
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ components:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
index_name:
|
||||||
|
name: index_name
|
||||||
|
in: path
|
||||||
|
description: name of the index
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
invalid_request:
|
invalid_request:
|
||||||
description: Invalid request
|
description: Invalid request
|
||||||
@@ -485,3 +492,22 @@ paths:
|
|||||||
$ref: "#/components/responses/unauthorized"
|
$ref: "#/components/responses/unauthorized"
|
||||||
"404":
|
"404":
|
||||||
$ref: "#/components/responses/not_found"
|
$ref: "#/components/responses/not_found"
|
||||||
|
/v1/table/{name}/index/{index_name}/drop/:
|
||||||
|
post:
|
||||||
|
description: Drop an index from the table
|
||||||
|
tags:
|
||||||
|
- Tables
|
||||||
|
summary: Drop an index from the table
|
||||||
|
operationId: dropIndex
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/table_name"
|
||||||
|
- $ref: "#/components/parameters/index_name"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Index successfully dropped
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/invalid_request"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/unauthorized"
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/not_found"
|
||||||
@@ -3,6 +3,7 @@ import * as vectordb from "vectordb";
|
|||||||
// --8<-- [end:import]
|
// --8<-- [end:import]
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
console.log("ann_indexes.ts: start");
|
||||||
// --8<-- [start:ingest]
|
// --8<-- [start:ingest]
|
||||||
const db = await vectordb.connect("data/sample-lancedb");
|
const db = await vectordb.connect("data/sample-lancedb");
|
||||||
|
|
||||||
@@ -49,5 +50,5 @@ import * as vectordb from "vectordb";
|
|||||||
.execute();
|
.execute();
|
||||||
// --8<-- [end:search3]
|
// --8<-- [end:search3]
|
||||||
|
|
||||||
console.log("Ann indexes: done");
|
console.log("ann_indexes.ts: done");
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ const example = async () => {
|
|||||||
// --8<-- [start:search]
|
// --8<-- [start:search]
|
||||||
const query = await tbl.search([100, 100]).limit(2).execute();
|
const query = await tbl.search([100, 100]).limit(2).execute();
|
||||||
// --8<-- [end:search]
|
// --8<-- [end:search]
|
||||||
console.log(query);
|
|
||||||
|
|
||||||
// --8<-- [start:delete]
|
// --8<-- [start:delete]
|
||||||
await tbl.delete('item = "fizz"');
|
await tbl.delete('item = "fizz"');
|
||||||
@@ -119,8 +118,9 @@ const example = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
console.log("basic_legacy.ts: start");
|
||||||
await example();
|
await example();
|
||||||
console.log("Basic example: done");
|
console.log("basic_legacy.ts: done");
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ Let's implement `SentenceTransformerEmbeddings` class. All you need to do is imp
|
|||||||
|
|
||||||
This is a stripped down version of our implementation of `SentenceTransformerEmbeddings` that removes certain optimizations and default settings.
|
This is a stripped down version of our implementation of `SentenceTransformerEmbeddings` that removes certain optimizations and default settings.
|
||||||
|
|
||||||
|
!!! danger "Use sensitive keys to prevent leaking secrets"
|
||||||
|
To prevent leaking secrets, such as API keys, you should add any sensitive
|
||||||
|
parameters of an embedding function to the output of the
|
||||||
|
[sensitive_keys()][lancedb.embeddings.base.EmbeddingFunction.sensitive_keys] /
|
||||||
|
[getSensitiveKeys()](../../js/namespaces/embedding/classes/EmbeddingFunction/#getsensitivekeys)
|
||||||
|
method. This prevents users from accidentally instantiating the embedding
|
||||||
|
function with hard-coded secrets.
|
||||||
|
|
||||||
Now you can use this embedding function to create your table schema and that's it! you can then ingest data and run queries without manually vectorizing the inputs.
|
Now you can use this embedding function to create your table schema and that's it! you can then ingest data and run queries without manually vectorizing the inputs.
|
||||||
|
|
||||||
=== "Python"
|
=== "Python"
|
||||||
|
|||||||
53
docs/src/embeddings/variables_and_secrets.md
Normal file
53
docs/src/embeddings/variables_and_secrets.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Variable and Secrets
|
||||||
|
|
||||||
|
Most embedding configuration options are saved in the table's metadata. However,
|
||||||
|
this isn't always appropriate. For example, API keys should never be stored in the
|
||||||
|
metadata. Additionally, other configuration options might be best set at runtime,
|
||||||
|
such as the `device` configuration that controls whether to use GPU or CPU for
|
||||||
|
inference. If you hardcoded this to GPU, you wouldn't be able to run the code on
|
||||||
|
a server without one.
|
||||||
|
|
||||||
|
To handle these cases, you can set variables on the embedding registry and
|
||||||
|
reference them in the embedding configuration. These variables will be available
|
||||||
|
during the runtime of your program, but not saved in the table's metadata. When
|
||||||
|
the table is loaded from a different process, the variables must be set again.
|
||||||
|
|
||||||
|
To set a variable, use the `set_var()` / `setVar()` method on the embedding registry.
|
||||||
|
To reference a variable, use the syntax `$env:VARIABLE_NAME`. If there is a default
|
||||||
|
value, you can use the syntax `$env:VARIABLE_NAME:DEFAULT_VALUE`.
|
||||||
|
|
||||||
|
## Using variables to set secrets
|
||||||
|
|
||||||
|
Sensitive configuration, such as API keys, must either be set as environment
|
||||||
|
variables or using variables on the embedding registry. If you pass in a hardcoded
|
||||||
|
value, LanceDB will raise an error. Instead, if you want to set an API key via
|
||||||
|
configuration, use a variable:
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
|
||||||
|
```python
|
||||||
|
--8<-- "python/python/tests/docs/test_embeddings_optional.py:register_secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Typescript"
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
--8<-- "nodejs/examples/embedding.test.ts:register_secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using variables to set the device parameter
|
||||||
|
|
||||||
|
Many embedding functions that run locally have a `device` parameter that controls
|
||||||
|
whether to use GPU or CPU for inference. Because not all computers have a GPU,
|
||||||
|
it's helpful to be able to set the `device` parameter at runtime, rather than
|
||||||
|
have it hard coded in the embedding configuration. To make it work even if the
|
||||||
|
variable isn't set, you could provide a default value of `cpu` in the embedding
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Some embedding libraries even have a method to detect which devices are available,
|
||||||
|
which could be used to dynamically set the device at runtime. For example, in Python
|
||||||
|
you can check if a CUDA GPU is available using `torch.cuda.is_available()`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
--8<-- "python/python/tests/docs/test_embeddings_optional.py:register_device"
|
||||||
|
```
|
||||||
1
docs/src/extra_js/reo.js
Normal file
1
docs/src/extra_js/reo.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(){var e,t,n;e="9627b71b382d201",t=function(){Reo.init({clientID:"9627b71b382d201"})},(n=document.createElement("script")).src="https://static.reo.dev/"+e+"/reo.js",n.defer=!0,n.onload=t,document.head.appendChild(n)}();
|
||||||
@@ -131,6 +131,20 @@ Return a brief description of the connection
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### dropAllTables()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract dropAllTables(): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Drop all tables in the database.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`void`>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### dropTable()
|
### dropTable()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ when creating a table or adding data to it)
|
|||||||
This function converts an array of Record<String, any> (row-major JS objects)
|
This function converts an array of Record<String, any> (row-major JS objects)
|
||||||
to an Arrow Table (a columnar structure)
|
to an Arrow Table (a columnar structure)
|
||||||
|
|
||||||
Note that it currently does not support nulls.
|
|
||||||
|
|
||||||
If a schema is provided then it will be used to determine the resulting array
|
If a schema is provided then it will be used to determine the resulting array
|
||||||
types. Fields will also be reordered to fit the order defined by the schema.
|
types. Fields will also be reordered to fit the order defined by the schema.
|
||||||
|
|
||||||
@@ -31,6 +29,9 @@ If a schema is not provided then the types will be inferred and the field order
|
|||||||
will be controlled by the order of properties in the first record. If a type
|
will be controlled by the order of properties in the first record. If a type
|
||||||
is inferred it will always be nullable.
|
is inferred it will always be nullable.
|
||||||
|
|
||||||
|
If not all fields are found in the data, then a subset of the schema will be
|
||||||
|
returned.
|
||||||
|
|
||||||
If the input is empty then a schema must be provided to create an empty table.
|
If the input is empty then a schema must be provided to create an empty table.
|
||||||
|
|
||||||
When a schema is not specified then data types will be inferred. The inference
|
When a schema is not specified then data types will be inferred. The inference
|
||||||
@@ -38,6 +39,7 @@ rules are as follows:
|
|||||||
|
|
||||||
- boolean => Bool
|
- boolean => Bool
|
||||||
- number => Float64
|
- number => Float64
|
||||||
|
- bigint => Int64
|
||||||
- String => Utf8
|
- String => Utf8
|
||||||
- Buffer => Binary
|
- Buffer => Binary
|
||||||
- Record<String, any> => Struct
|
- Record<String, any> => Struct
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
|
### extraHeaders?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional extraHeaders: Record<string, string>;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### retryConfig?
|
### retryConfig?
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
### dataStorageVersion?
|
### ~~dataStorageVersion?~~
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional dataStorageVersion: string;
|
optional dataStorageVersion: string;
|
||||||
@@ -19,6 +19,10 @@ The version of the data storage format to use.
|
|||||||
The default is `stable`.
|
The default is `stable`.
|
||||||
Set to "legacy" to use the old format.
|
Set to "legacy" to use the old format.
|
||||||
|
|
||||||
|
#### Deprecated
|
||||||
|
|
||||||
|
Pass `new_table_data_storage_version` to storageOptions instead.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### embeddingFunction?
|
### embeddingFunction?
|
||||||
@@ -29,7 +33,7 @@ optional embeddingFunction: EmbeddingFunctionConfig;
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### enableV2ManifestPaths?
|
### ~~enableV2ManifestPaths?~~
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional enableV2ManifestPaths: boolean;
|
optional enableV2ManifestPaths: boolean;
|
||||||
@@ -41,6 +45,10 @@ turning this on will make the dataset unreadable for older versions
|
|||||||
of LanceDB (prior to 0.10.0). To migrate an existing dataset, instead
|
of LanceDB (prior to 0.10.0). To migrate an existing dataset, instead
|
||||||
use the LocalTable#migrateManifestPathsV2 method.
|
use the LocalTable#migrateManifestPathsV2 method.
|
||||||
|
|
||||||
|
#### Deprecated
|
||||||
|
|
||||||
|
Pass `new_table_enable_v2_manifest_paths` to storageOptions instead.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### existOk
|
### existOk
|
||||||
@@ -90,17 +98,3 @@ Options already set on the connection will be inherited by the table,
|
|||||||
but can be overridden here.
|
but can be overridden here.
|
||||||
|
|
||||||
The available options are described at https://lancedb.github.io/lancedb/guides/storage/
|
The available options are described at https://lancedb.github.io/lancedb/guides/storage/
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### useLegacyFormat?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional useLegacyFormat: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
If true then data files will be written with the legacy format
|
|
||||||
|
|
||||||
The default is false.
|
|
||||||
|
|
||||||
Deprecated. Use data storage version instead.
|
|
||||||
|
|||||||
@@ -8,6 +8,23 @@
|
|||||||
|
|
||||||
An embedding function that automatically creates vector representation for a given column.
|
An embedding function that automatically creates vector representation for a given column.
|
||||||
|
|
||||||
|
It's important subclasses pass the **original** options to the super constructor
|
||||||
|
and then pass those options to `resolveVariables` to resolve any variables before
|
||||||
|
using them.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class MyEmbeddingFunction extends EmbeddingFunction {
|
||||||
|
constructor(options: {model: string, timeout: number}) {
|
||||||
|
super(optionsRaw);
|
||||||
|
const options = this.resolveVariables(optionsRaw);
|
||||||
|
this.model = options.model;
|
||||||
|
this.timeout = options.timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Extended by
|
## Extended by
|
||||||
|
|
||||||
- [`TextEmbeddingFunction`](TextEmbeddingFunction.md)
|
- [`TextEmbeddingFunction`](TextEmbeddingFunction.md)
|
||||||
@@ -82,12 +99,33 @@ The datatype of the embeddings
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### getSensitiveKeys()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
protected getSensitiveKeys(): string[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Provide a list of keys in the function options that should be treated as
|
||||||
|
sensitive. If users pass raw values for these keys, they will be rejected.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### init()?
|
### init()?
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional init(): Promise<void>
|
optional init(): Promise<void>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optionally load any resources needed for the embedding function.
|
||||||
|
|
||||||
|
This method is called after the embedding function has been initialized
|
||||||
|
but before any embeddings are computed. It is useful for loading local models
|
||||||
|
or other resources that are needed for the embedding function to work.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<`void`>
|
||||||
@@ -108,6 +146,24 @@ The number of dimensions of the embeddings
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### resolveVariables()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
protected resolveVariables(config): Partial<M>
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply variables to the config.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **config**: `Partial`<`M`>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Partial`<`M`>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### sourceField()
|
### sourceField()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -134,37 +190,15 @@ sourceField is used in combination with `LanceSchema` to provide a declarative d
|
|||||||
### toJSON()
|
### toJSON()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract toJSON(): Partial<M>
|
toJSON(): Record<string, any>
|
||||||
```
|
```
|
||||||
|
|
||||||
Convert the embedding function to a JSON object
|
Get the original arguments to the constructor, to serialize them so they
|
||||||
It is used to serialize the embedding function to the schema
|
can be used to recreate the embedding function later.
|
||||||
It's important that any object returned by this method contains all the necessary
|
|
||||||
information to recreate the embedding function
|
|
||||||
|
|
||||||
It should return the same object that was passed to the constructor
|
|
||||||
If it does not, the embedding function will not be able to be recreated, or could be recreated incorrectly
|
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Partial`<`M`>
|
`Record`<`string`, `any`>
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
class MyEmbeddingFunction extends EmbeddingFunction {
|
|
||||||
constructor(options: {model: string, timeout: number}) {
|
|
||||||
super();
|
|
||||||
this.model = options.model;
|
|
||||||
this.timeout = options.timeout;
|
|
||||||
}
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
model: this.model,
|
|
||||||
timeout: this.timeout,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,28 @@ getTableMetadata(functions): Map<string, string>
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### getVar()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getVar(name): undefined | string
|
||||||
|
```
|
||||||
|
|
||||||
|
Get a variable.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **name**: `string`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`undefined` \| `string`
|
||||||
|
|
||||||
|
#### See
|
||||||
|
|
||||||
|
[setVar](EmbeddingFunctionRegistry.md#setvar)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### length()
|
### length()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -145,3 +167,31 @@ reset the registry to the initial state
|
|||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`void`
|
`void`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### setVar()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
setVar(name, value): void
|
||||||
|
```
|
||||||
|
|
||||||
|
Set a variable. These can be accessed in the embedding function
|
||||||
|
configuration using the syntax `$var:variable_name`. If they are not
|
||||||
|
set, an error will be thrown letting you know which key is unset. If you
|
||||||
|
want to supply a default value, you can add an additional part in the
|
||||||
|
configuration like so: `$var:variable_name:default_value`. Default values
|
||||||
|
can be used for runtime configurations that are not sensitive, such as
|
||||||
|
whether to use a GPU for inference.
|
||||||
|
|
||||||
|
The name must not contain colons. The default value can contain colons.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **name**: `string`
|
||||||
|
|
||||||
|
* **value**: `string`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`void`
|
||||||
|
|||||||
@@ -114,12 +114,37 @@ abstract generateEmbeddings(texts, ...args): Promise<number[][] | Float32Array[]
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### getSensitiveKeys()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
protected getSensitiveKeys(): string[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Provide a list of keys in the function options that should be treated as
|
||||||
|
sensitive. If users pass raw values for these keys, they will be rejected.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`EmbeddingFunction`](EmbeddingFunction.md).[`getSensitiveKeys`](EmbeddingFunction.md#getsensitivekeys)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### init()?
|
### init()?
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional init(): Promise<void>
|
optional init(): Promise<void>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optionally load any resources needed for the embedding function.
|
||||||
|
|
||||||
|
This method is called after the embedding function has been initialized
|
||||||
|
but before any embeddings are computed. It is useful for loading local models
|
||||||
|
or other resources that are needed for the embedding function to work.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<`void`>
|
||||||
@@ -148,6 +173,28 @@ The number of dimensions of the embeddings
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### resolveVariables()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
protected resolveVariables(config): Partial<M>
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply variables to the config.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **config**: `Partial`<`M`>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Partial`<`M`>
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`EmbeddingFunction`](EmbeddingFunction.md).[`resolveVariables`](EmbeddingFunction.md#resolvevariables)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### sourceField()
|
### sourceField()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -173,37 +220,15 @@ sourceField is used in combination with `LanceSchema` to provide a declarative d
|
|||||||
### toJSON()
|
### toJSON()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract toJSON(): Partial<M>
|
toJSON(): Record<string, any>
|
||||||
```
|
```
|
||||||
|
|
||||||
Convert the embedding function to a JSON object
|
Get the original arguments to the constructor, to serialize them so they
|
||||||
It is used to serialize the embedding function to the schema
|
can be used to recreate the embedding function later.
|
||||||
It's important that any object returned by this method contains all the necessary
|
|
||||||
information to recreate the embedding function
|
|
||||||
|
|
||||||
It should return the same object that was passed to the constructor
|
|
||||||
If it does not, the embedding function will not be able to be recreated, or could be recreated incorrectly
|
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Partial`<`M`>
|
`Record`<`string`, `any`>
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
class MyEmbeddingFunction extends EmbeddingFunction {
|
|
||||||
constructor(options: {model: string, timeout: number}) {
|
|
||||||
super();
|
|
||||||
this.model = options.model;
|
|
||||||
this.timeout = options.timeout;
|
|
||||||
}
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
model: this.model,
|
|
||||||
timeout: this.timeout,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Inherited from
|
#### Inherited from
|
||||||
|
|
||||||
|
|||||||
@@ -9,23 +9,50 @@ LanceDB supports [Polars](https://github.com/pola-rs/polars), a blazingly fast D
|
|||||||
|
|
||||||
First, we connect to a LanceDB database.
|
First, we connect to a LanceDB database.
|
||||||
|
|
||||||
|
=== "Sync API"
|
||||||
|
|
||||||
|
```py
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:import-lancedb"
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:connect_to_lancedb"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Async API"
|
||||||
|
|
||||||
|
```py
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:import-lancedb"
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:connect_to_lancedb_async"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
|
||||||
--8<-- "python/python/tests/docs/test_python.py:import-lancedb"
|
|
||||||
--8<-- "python/python/tests/docs/test_python.py:connect_to_lancedb"
|
|
||||||
```
|
|
||||||
|
|
||||||
We can load a Polars `DataFrame` to LanceDB directly.
|
We can load a Polars `DataFrame` to LanceDB directly.
|
||||||
|
|
||||||
```py
|
=== "Sync API"
|
||||||
--8<-- "python/python/tests/docs/test_python.py:import-polars"
|
|
||||||
--8<-- "python/python/tests/docs/test_python.py:create_table_polars"
|
```py
|
||||||
```
|
--8<-- "python/python/tests/docs/test_python.py:import-polars"
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:create_table_polars"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Async API"
|
||||||
|
|
||||||
|
```py
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:import-polars"
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:create_table_polars_async"
|
||||||
|
```
|
||||||
|
|
||||||
We can now perform similarity search via the LanceDB Python API.
|
We can now perform similarity search via the LanceDB Python API.
|
||||||
|
|
||||||
```py
|
=== "Sync API"
|
||||||
--8<-- "python/python/tests/docs/test_python.py:vector_search_polars"
|
|
||||||
```
|
```py
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:vector_search_polars"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Async API"
|
||||||
|
|
||||||
|
```py
|
||||||
|
--8<-- "python/python/tests/docs/test_python.py:vector_search_polars_async"
|
||||||
|
```
|
||||||
|
|
||||||
In addition to the selected columns, LanceDB also returns a vector
|
In addition to the selected columns, LanceDB also returns a vector
|
||||||
and also the `_distance` column which is the distance between the query
|
and also the `_distance` column which is the distance between the query
|
||||||
@@ -112,4 +139,3 @@ The reason it's beneficial to not convert the LanceDB Table
|
|||||||
to a DataFrame is because the table can potentially be way larger
|
to a DataFrame is because the table can potentially be way larger
|
||||||
than memory, and Polars LazyFrames allow us to work with such
|
than memory, and Polars LazyFrames allow us to work with such
|
||||||
larger-than-memory datasets by not loading it into memory all at once.
|
larger-than-memory datasets by not loading it into memory all at once.
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
|
|
||||||
[Pydantic](https://docs.pydantic.dev/latest/) is a data validation library in Python.
|
[Pydantic](https://docs.pydantic.dev/latest/) is a data validation library in Python.
|
||||||
LanceDB integrates with Pydantic for schema inference, data ingestion, and query result casting.
|
LanceDB integrates with Pydantic for schema inference, data ingestion, and query result casting.
|
||||||
|
Using [LanceModel][lancedb.pydantic.LanceModel], users can seamlessly
|
||||||
|
integrate Pydantic with the rest of the LanceDB APIs.
|
||||||
|
|
||||||
## Schema
|
```python
|
||||||
|
|
||||||
LanceDB supports to create Apache Arrow Schema from a
|
--8<-- "python/python/tests/docs/test_pydantic_integration.py:imports"
|
||||||
[Pydantic BaseModel](https://docs.pydantic.dev/latest/api/main/#pydantic.main.BaseModel)
|
|
||||||
via [pydantic_to_schema()](python.md#lancedb.pydantic.pydantic_to_schema) method.
|
--8<-- "python/python/tests/docs/test_pydantic_integration.py:base_model"
|
||||||
|
|
||||||
|
--8<-- "python/python/tests/docs/test_pydantic_integration.py:set_url"
|
||||||
|
--8<-- "python/python/tests/docs/test_pydantic_integration.py:base_example"
|
||||||
|
```
|
||||||
|
|
||||||
::: lancedb.pydantic.pydantic_to_schema
|
|
||||||
|
|
||||||
## Vector Field
|
## Vector Field
|
||||||
|
|
||||||
@@ -34,3 +39,9 @@ Current supported type conversions:
|
|||||||
| `list` | `pyarrow.List` |
|
| `list` | `pyarrow.List` |
|
||||||
| `BaseModel` | `pyarrow.Struct` |
|
| `BaseModel` | `pyarrow.Struct` |
|
||||||
| `Vector(n)` | `pyarrow.FixedSizeList(float32, n)` |
|
| `Vector(n)` | `pyarrow.FixedSizeList(float32, n)` |
|
||||||
|
|
||||||
|
LanceDB supports to create Apache Arrow Schema from a
|
||||||
|
[Pydantic BaseModel][pydantic.BaseModel]
|
||||||
|
via [pydantic_to_schema()](python.md#lancedb.pydantic.pydantic_to_schema) method.
|
||||||
|
|
||||||
|
::: lancedb.pydantic.pydantic_to_schema
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ async function setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async () => {
|
async () => {
|
||||||
|
console.log("search_legacy.ts: start");
|
||||||
await setup();
|
await setup();
|
||||||
|
|
||||||
// --8<-- [start:search1]
|
// --8<-- [start:search1]
|
||||||
@@ -37,5 +38,5 @@ async () => {
|
|||||||
.execute();
|
.execute();
|
||||||
// --8<-- [end:search2]
|
// --8<-- [end:search2]
|
||||||
|
|
||||||
console.log("search: done");
|
console.log("search_legacy.ts: done");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as vectordb from "vectordb";
|
import * as vectordb from "vectordb";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
console.log("sql_legacy.ts: start");
|
||||||
const db = await vectordb.connect("data/sample-lancedb");
|
const db = await vectordb.connect("data/sample-lancedb");
|
||||||
|
|
||||||
let data = [];
|
let data = [];
|
||||||
@@ -34,5 +35,5 @@ import * as vectordb from "vectordb";
|
|||||||
await tbl.filter("id = 10").limit(10).execute();
|
await tbl.filter("id = 10").limit(10).execute();
|
||||||
// --8<-- [end:sql_search]
|
// --8<-- [end:sql_search]
|
||||||
|
|
||||||
console.log("SQL search: done");
|
console.log("sql_legacy.ts: done");
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ excluded_globs = [
|
|||||||
"../src/python/duckdb.md",
|
"../src/python/duckdb.md",
|
||||||
"../src/python/pandas_and_pyarrow.md",
|
"../src/python/pandas_and_pyarrow.md",
|
||||||
"../src/python/polars_arrow.md",
|
"../src/python/polars_arrow.md",
|
||||||
|
"../src/python/pydantic.md",
|
||||||
"../src/embeddings/*.md",
|
"../src/embeddings/*.md",
|
||||||
"../src/concepts/*.md",
|
"../src/concepts/*.md",
|
||||||
"../src/ann_indexes.md",
|
"../src/ann_indexes.md",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.15.1-beta.2</version>
|
<version>0.18.0-final.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.15.1-beta.2</version>
|
<version>0.18.0-final.0</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>LanceDB Parent</name>
|
<name>LanceDB Parent</name>
|
||||||
|
|||||||
68
node/package-lock.json
generated
68
node/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
@@ -52,14 +52,14 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.15.1-beta.2",
|
"@lancedb/vectordb-darwin-arm64": "0.18.0",
|
||||||
"@lancedb/vectordb-darwin-x64": "0.15.1-beta.2",
|
"@lancedb/vectordb-darwin-x64": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-arm64-gnu": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-arm64-musl": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-arm64-musl": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-x64-gnu": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-x64-musl": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-x64-musl": "0.18.0",
|
||||||
"@lancedb/vectordb-win32-arm64-msvc": "0.15.1-beta.2",
|
"@lancedb/vectordb-win32-arm64-msvc": "0.18.0",
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.15.1-beta.2"
|
"@lancedb/vectordb-win32-x64-msvc": "0.18.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@apache-arrow/ts": "^14.0.2",
|
"@apache-arrow/ts": "^14.0.2",
|
||||||
@@ -330,9 +330,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-darwin-arm64": {
|
"node_modules/@lancedb/vectordb-darwin-arm64": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-arm64/-/vectordb-darwin-arm64-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-arm64/-/vectordb-darwin-arm64-0.18.0.tgz",
|
||||||
"integrity": "sha512-hq5VkIW7oP+S030+o14TfSnsanjtKNuYMtv4ANBAsV7Lwr/q7EKwMZLwrbLW4Y/1hrjTjdwZF2ePgd5UwXdq1w==",
|
"integrity": "sha512-ormNCmny1j64aSZRrZeUQ1Zs8cOFKrW14NgTmW3AehDuru+Ep+8AriHA5Pmyi6raBOZfNzDSiZs/LTzzyVaa7g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -343,9 +343,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-darwin-x64": {
|
"node_modules/@lancedb/vectordb-darwin-x64": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-x64/-/vectordb-darwin-x64-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-x64/-/vectordb-darwin-x64-0.18.0.tgz",
|
||||||
"integrity": "sha512-Xvms7y1PG52gDlbaWSotYjYhiT7oF9eF8T29H6wk5FA43Eu6fg1+wrrvMR8+7gDfwjsCN3kNabk9Cu4DDYtldg==",
|
"integrity": "sha512-S4skQ1RXXQJciq40s84Kyy7v3YC+nao8pX4xUyxDcKRx+90Qg9eH+tehs6XLN1IjrQT/9CWKaE5wxZmv6Oys4g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -356,9 +356,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-linux-arm64-gnu": {
|
"node_modules/@lancedb/vectordb-linux-arm64-gnu": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-gnu/-/vectordb-linux-arm64-gnu-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-gnu/-/vectordb-linux-arm64-gnu-0.18.0.tgz",
|
||||||
"integrity": "sha512-jTJEan8TLTtly1cucWvQZrh3N7fuJWDBz+ADmHUMQbRH6SDUoGqIekgs0u1WrgYuLFIPAi6V1VI66qbCWncCLQ==",
|
"integrity": "sha512-1txr4tasVdxy321/4Fw8GJPjzrf84F02L9ffN8JebHmmR0S8uk2MKf2WsyLaSVRPd4YHIvvf3qmG0RGaUsb2sw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -369,9 +369,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-linux-arm64-musl": {
|
"node_modules/@lancedb/vectordb-linux-arm64-musl": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-musl/-/vectordb-linux-arm64-musl-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-musl/-/vectordb-linux-arm64-musl-0.18.0.tgz",
|
||||||
"integrity": "sha512-KRTcMTsMIdkYz2u1BZ5xPB8q1unyyKEXXQZSj4Sye0vP5lBm1vvH4IAwZ3BkxffULFDLHpPegGSnnq0xNBWp2g==",
|
"integrity": "sha512-8xS1xaoJeFDx6WmDBcfueWvIbdNX/ptQXfoC7hYICwNHizjlyt4O3Nxz8uG9URMF1y9saUYUditIHLzLVZc76g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -382,9 +382,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-linux-x64-gnu": {
|
"node_modules/@lancedb/vectordb-linux-x64-gnu": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-gnu/-/vectordb-linux-x64-gnu-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-gnu/-/vectordb-linux-x64-gnu-0.18.0.tgz",
|
||||||
"integrity": "sha512-oILD1M8BYAgNmdIuK1jqueqVtdkAvXfpuMNMOiUq2NhUaa/vFxO6Tb2XR8tOWoE14/VJkWTLpk+nLhjgfiNNTA==",
|
"integrity": "sha512-8XUc2UnEV3awv0DGJS5gRA7yTkicX6oPN7GudXXxycCKL33FJ2ah7hkeDia9Bhk9MmvTonvsEDvUSqnglcpqfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -395,9 +395,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-linux-x64-musl": {
|
"node_modules/@lancedb/vectordb-linux-x64-musl": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-musl/-/vectordb-linux-x64-musl-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-musl/-/vectordb-linux-x64-musl-0.18.0.tgz",
|
||||||
"integrity": "sha512-LO3WE7UUDOJIAN4hifTpaX8tzjfXRtTghqCilUGei8uaZbOD2T1pJKT89Ub7AurNVr3NEWT4NYu/Fa7ilfkb6w==",
|
"integrity": "sha512-LV7TuWgLcL82Wdq+EH2Xs3+apqeLohwYLlVIauVAwKEHvdwyNxTOW9TaNLvHXcbylIh7agl2xXvASCNhYZAyzA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -408,9 +408,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-win32-arm64-msvc": {
|
"node_modules/@lancedb/vectordb-win32-arm64-msvc": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-arm64-msvc/-/vectordb-win32-arm64-msvc-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-arm64-msvc/-/vectordb-win32-arm64-msvc-0.18.0.tgz",
|
||||||
"integrity": "sha512-5H52qrtS1GIif6HC3lvuilKpzKhPJNkkAB5R9bx7ly2EN3d+ZFF4fwdlXhyoUaHI0SUf7z00hzgIVHqWnDadCg==",
|
"integrity": "sha512-kxdCnKfvnuDKoKZRUBbreMBpimHb+k9/pFR48GN6JSrIuzUDx5G1VjHKBmaFhbveZCOBjjtYlg/8ohnWQHZfeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -421,9 +421,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-win32-x64-msvc": {
|
"node_modules/@lancedb/vectordb-win32-x64-msvc": {
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-x64-msvc/-/vectordb-win32-x64-msvc-0.15.1-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-x64-msvc/-/vectordb-win32-x64-msvc-0.18.0.tgz",
|
||||||
"integrity": "sha512-9sqH6uw8WWgQSZPzHCV9Ncurlx2YMwr6dnQtQGQ/KTDm7n/V2bz9m0t9+07jhyVzfJIxBkR/UlDMC3U3M/uAuQ==",
|
"integrity": "sha512-uAE80q50cAp4gHoGvclxJqZGqj3/9oN9kz8iXgNIxiPngqnN01kVyaj4ulm4Qk/nauWUhHJ3tjTh/+CpkhSc2Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"description": " Serverless, low-latency vector database for AI applications",
|
"description": " Serverless, low-latency vector database for AI applications",
|
||||||
"private": false,
|
"private": false,
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -92,13 +92,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@lancedb/vectordb-darwin-x64": "0.15.1-beta.2",
|
"@lancedb/vectordb-darwin-x64": "0.18.0",
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.15.1-beta.2",
|
"@lancedb/vectordb-darwin-arm64": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-x64-gnu": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-arm64-gnu": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-x64-musl": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-x64-musl": "0.18.0",
|
||||||
"@lancedb/vectordb-linux-arm64-musl": "0.15.1-beta.2",
|
"@lancedb/vectordb-linux-arm64-musl": "0.18.0",
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.15.1-beta.2",
|
"@lancedb/vectordb-win32-x64-msvc": "0.18.0",
|
||||||
"@lancedb/vectordb-win32-arm64-msvc": "0.15.1-beta.2"
|
"@lancedb/vectordb-win32-arm64-msvc": "0.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ const {
|
|||||||
tableSchema,
|
tableSchema,
|
||||||
tableAddColumns,
|
tableAddColumns,
|
||||||
tableAlterColumns,
|
tableAlterColumns,
|
||||||
tableDropColumns
|
tableDropColumns,
|
||||||
|
tableDropIndex
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
} = require("../native.js");
|
} = require("../native.js");
|
||||||
|
|
||||||
@@ -604,6 +605,13 @@ export interface Table<T = number[]> {
|
|||||||
*/
|
*/
|
||||||
dropColumns(columnNames: string[]): Promise<void>
|
dropColumns(columnNames: string[]): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop an index from the table
|
||||||
|
*
|
||||||
|
* @param indexName The name of the index to drop
|
||||||
|
*/
|
||||||
|
dropIndex(indexName: string): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrument the behavior of this Table with middleware.
|
* Instrument the behavior of this Table with middleware.
|
||||||
*
|
*
|
||||||
@@ -1206,6 +1214,10 @@ export class LocalTable<T = number[]> implements Table<T> {
|
|||||||
return tableDropColumns.call(this._tbl, columnNames);
|
return tableDropColumns.call(this._tbl, columnNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async dropIndex(indexName: string): Promise<void> {
|
||||||
|
return tableDropIndex.call(this._tbl, indexName);
|
||||||
|
}
|
||||||
|
|
||||||
withMiddleware(middleware: HttpMiddleware): Table<T> {
|
withMiddleware(middleware: HttpMiddleware): Table<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,6 +471,18 @@ export class RemoteTable<T = number[]> implements Table<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async dropIndex (index_name: string): Promise<void> {
|
||||||
|
const res = await this._client.post(
|
||||||
|
`/v1/table/${encodeURIComponent(this._name)}/index/${encodeURIComponent(index_name)}/drop/`
|
||||||
|
)
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(
|
||||||
|
`Server Error, status: ${res.status}, ` +
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
`message: ${res.statusText}: ${await res.body()}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async countRows (filter?: string): Promise<number> {
|
async countRows (filter?: string): Promise<number> {
|
||||||
const result = await this._client.post(`/v1/table/${encodeURIComponent(this._name)}/count_rows/`, {
|
const result = await this._client.post(`/v1/table/${encodeURIComponent(this._name)}/count_rows/`, {
|
||||||
|
|||||||
@@ -894,6 +894,27 @@ describe("LanceDB client", function () {
|
|||||||
expect(stats.distanceType).to.equal("l2");
|
expect(stats.distanceType).to.equal("l2");
|
||||||
expect(stats.numIndices).to.equal(1);
|
expect(stats.numIndices).to.equal(1);
|
||||||
}).timeout(50_000);
|
}).timeout(50_000);
|
||||||
|
|
||||||
|
// not yet implemented
|
||||||
|
// it("can drop index", async function () {
|
||||||
|
// const uri = await createTestDB(32, 300);
|
||||||
|
// const con = await lancedb.connect(uri);
|
||||||
|
// const table = await con.openTable("vectors");
|
||||||
|
// await table.createIndex({
|
||||||
|
// type: "ivf_pq",
|
||||||
|
// column: "vector",
|
||||||
|
// num_partitions: 2,
|
||||||
|
// max_iters: 2,
|
||||||
|
// num_sub_vectors: 2
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const indices = await table.listIndices();
|
||||||
|
// expect(indices).to.have.lengthOf(1);
|
||||||
|
// expect(indices[0].name).to.equal("vector_idx");
|
||||||
|
//
|
||||||
|
// await table.dropIndex("vector_idx");
|
||||||
|
// expect(await table.listIndices()).to.have.lengthOf(0);
|
||||||
|
// }).timeout(50_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when using a custom embedding function", function () {
|
describe("when using a custom embedding function", function () {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.15.1-beta.2"
|
version = "0.18.0"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
Float64,
|
Float64,
|
||||||
Struct,
|
Struct,
|
||||||
List,
|
List,
|
||||||
|
Int16,
|
||||||
Int32,
|
Int32,
|
||||||
Int64,
|
Int64,
|
||||||
Float,
|
Float,
|
||||||
@@ -108,13 +109,16 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const table = (await tableCreationMethod(
|
const table = (await tableCreationMethod(
|
||||||
records,
|
records,
|
||||||
recordsReversed,
|
recordsReversed,
|
||||||
schema,
|
schema,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
)) as any;
|
)) as any;
|
||||||
|
|
||||||
|
// We expect deterministic ordering of the fields
|
||||||
|
expect(table.schema.names).toEqual(schema.names);
|
||||||
|
|
||||||
schema.fields.forEach(
|
schema.fields.forEach(
|
||||||
(
|
(
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
@@ -141,13 +145,13 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
describe("The function makeArrowTable", function () {
|
describe("The function makeArrowTable", function () {
|
||||||
it("will use data types from a provided schema instead of inference", async function () {
|
it("will use data types from a provided schema instead of inference", async function () {
|
||||||
const schema = new Schema([
|
const schema = new Schema([
|
||||||
new Field("a", new Int32()),
|
new Field("a", new Int32(), false),
|
||||||
new Field("b", new Float32()),
|
new Field("b", new Float32(), true),
|
||||||
new Field(
|
new Field(
|
||||||
"c",
|
"c",
|
||||||
new FixedSizeList(3, new Field("item", new Float16())),
|
new FixedSizeList(3, new Field("item", new Float16())),
|
||||||
),
|
),
|
||||||
new Field("d", new Int64()),
|
new Field("d", new Int64(), true),
|
||||||
]);
|
]);
|
||||||
const table = makeArrowTable(
|
const table = makeArrowTable(
|
||||||
[
|
[
|
||||||
@@ -165,12 +169,15 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
expect(actual.numRows).toBe(3);
|
expect(actual.numRows).toBe(3);
|
||||||
const actualSchema = actual.schema;
|
const actualSchema = actual.schema;
|
||||||
expect(actualSchema).toEqual(schema);
|
expect(actualSchema).toEqual(schema);
|
||||||
|
expect(table.getChild("a")?.toJSON()).toEqual([1, 4, 7]);
|
||||||
|
expect(table.getChild("b")?.toJSON()).toEqual([2, 5, 8]);
|
||||||
|
expect(table.getChild("d")?.toJSON()).toEqual([9n, 10n, null]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("will assume the column `vector` is FixedSizeList<Float32> by default", async function () {
|
it("will assume the column `vector` is FixedSizeList<Float32> by default", async function () {
|
||||||
const schema = new Schema([
|
const schema = new Schema([
|
||||||
new Field("a", new Float(Precision.DOUBLE), true),
|
new Field("a", new Float(Precision.DOUBLE), true),
|
||||||
new Field("b", new Float(Precision.DOUBLE), true),
|
new Field("b", new Int64(), true),
|
||||||
new Field(
|
new Field(
|
||||||
"vector",
|
"vector",
|
||||||
new FixedSizeList(
|
new FixedSizeList(
|
||||||
@@ -181,9 +188,9 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
const table = makeArrowTable([
|
const table = makeArrowTable([
|
||||||
{ a: 1, b: 2, vector: [1, 2, 3] },
|
{ a: 1, b: 2n, vector: [1, 2, 3] },
|
||||||
{ a: 4, b: 5, vector: [4, 5, 6] },
|
{ a: 4, b: 5n, vector: [4, 5, 6] },
|
||||||
{ a: 7, b: 8, vector: [7, 8, 9] },
|
{ a: 7, b: 8n, vector: [7, 8, 9] },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const buf = await fromTableToBuffer(table);
|
const buf = await fromTableToBuffer(table);
|
||||||
@@ -193,6 +200,19 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
expect(actual.numRows).toBe(3);
|
expect(actual.numRows).toBe(3);
|
||||||
const actualSchema = actual.schema;
|
const actualSchema = actual.schema;
|
||||||
expect(actualSchema).toEqual(schema);
|
expect(actualSchema).toEqual(schema);
|
||||||
|
|
||||||
|
expect(table.getChild("a")?.toJSON()).toEqual([1, 4, 7]);
|
||||||
|
expect(table.getChild("b")?.toJSON()).toEqual([2n, 5n, 8n]);
|
||||||
|
expect(
|
||||||
|
table
|
||||||
|
.getChild("vector")
|
||||||
|
?.toJSON()
|
||||||
|
.map((v) => v.toJSON()),
|
||||||
|
).toEqual([
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
[7, 8, 9],
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can support multiple vector columns", async function () {
|
it("can support multiple vector columns", async function () {
|
||||||
@@ -206,7 +226,7 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
),
|
),
|
||||||
new Field(
|
new Field(
|
||||||
"vec2",
|
"vec2",
|
||||||
new FixedSizeList(3, new Field("item", new Float16(), true)),
|
new FixedSizeList(3, new Field("item", new Float64(), true)),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@@ -219,7 +239,7 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
{
|
{
|
||||||
vectorColumns: {
|
vectorColumns: {
|
||||||
vec1: { type: new Float16() },
|
vec1: { type: new Float16() },
|
||||||
vec2: { type: new Float16() },
|
vec2: { type: new Float64() },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -307,6 +327,53 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("will allow subsets of columns if nullable", async function () {
|
||||||
|
const schema = new Schema([
|
||||||
|
new Field("a", new Int64(), true),
|
||||||
|
new Field(
|
||||||
|
"s",
|
||||||
|
new Struct([
|
||||||
|
new Field("x", new Int32(), true),
|
||||||
|
new Field("y", new Int32(), true),
|
||||||
|
]),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
new Field("d", new Int16(), true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const table = makeArrowTable([{ a: 1n }], { schema });
|
||||||
|
expect(table.numCols).toBe(1);
|
||||||
|
expect(table.numRows).toBe(1);
|
||||||
|
|
||||||
|
const table2 = makeArrowTable([{ a: 1n, d: 2 }], { schema });
|
||||||
|
expect(table2.numCols).toBe(2);
|
||||||
|
|
||||||
|
const table3 = makeArrowTable([{ s: { y: 3 } }], { schema });
|
||||||
|
expect(table3.numCols).toBe(1);
|
||||||
|
const expectedSchema = new Schema([
|
||||||
|
new Field("s", new Struct([new Field("y", new Int32(), true)]), true),
|
||||||
|
]);
|
||||||
|
expect(table3.schema).toEqual(expectedSchema);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will work even if columns are sparsely provided", async function () {
|
||||||
|
const sparseRecords = [{ a: 1n }, { b: 2n }, { c: 3n }, { d: 4n }];
|
||||||
|
const table = makeArrowTable(sparseRecords);
|
||||||
|
expect(table.numCols).toBe(4);
|
||||||
|
expect(table.numRows).toBe(4);
|
||||||
|
|
||||||
|
const schema = new Schema([
|
||||||
|
new Field("a", new Int64(), true),
|
||||||
|
new Field("b", new Int32(), true),
|
||||||
|
new Field("c", new Int64(), true),
|
||||||
|
new Field("d", new Int16(), true),
|
||||||
|
]);
|
||||||
|
const table2 = makeArrowTable(sparseRecords, { schema });
|
||||||
|
expect(table2.numCols).toBe(4);
|
||||||
|
expect(table2.numRows).toBe(4);
|
||||||
|
expect(table2.schema).toEqual(schema);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class DummyEmbedding extends EmbeddingFunction<string> {
|
class DummyEmbedding extends EmbeddingFunction<string> {
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ describe("when connecting", () => {
|
|||||||
it("should connect", async () => {
|
it("should connect", async () => {
|
||||||
const db = await connect(tmpDir.name);
|
const db = await connect(tmpDir.name);
|
||||||
expect(db.display()).toBe(
|
expect(db.display()).toBe(
|
||||||
`NativeDatabase(uri=${tmpDir.name}, read_consistency_interval=None)`,
|
`ListingDatabase(uri=${tmpDir.name}, read_consistency_interval=None)`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow read consistency interval to be specified", async () => {
|
it("should allow read consistency interval to be specified", async () => {
|
||||||
const db = await connect(tmpDir.name, { readConsistencyInterval: 5 });
|
const db = await connect(tmpDir.name, { readConsistencyInterval: 5 });
|
||||||
expect(db.display()).toBe(
|
expect(db.display()).toBe(
|
||||||
`NativeDatabase(uri=${tmpDir.name}, read_consistency_interval=5s)`,
|
`ListingDatabase(uri=${tmpDir.name}, read_consistency_interval=5s)`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -61,6 +61,26 @@ describe("given a connection", () => {
|
|||||||
await expect(tbl.countRows()).resolves.toBe(1);
|
await expect(tbl.countRows()).resolves.toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should be able to drop tables`", async () => {
|
||||||
|
await db.createTable("test", [{ id: 1 }, { id: 2 }]);
|
||||||
|
await db.createTable("test2", [{ id: 1 }, { id: 2 }]);
|
||||||
|
await db.createTable("test3", [{ id: 1 }, { id: 2 }]);
|
||||||
|
|
||||||
|
await expect(db.tableNames()).resolves.toEqual(["test", "test2", "test3"]);
|
||||||
|
|
||||||
|
await db.dropTable("test2");
|
||||||
|
|
||||||
|
await expect(db.tableNames()).resolves.toEqual(["test", "test3"]);
|
||||||
|
|
||||||
|
await db.dropAllTables();
|
||||||
|
|
||||||
|
await expect(db.tableNames()).resolves.toEqual([]);
|
||||||
|
|
||||||
|
// Make sure we can still create more tables after dropping all
|
||||||
|
|
||||||
|
await db.createTable("test4", [{ id: 1 }, { id: 2 }]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should fail if creating table twice, unless overwrite is true", async () => {
|
it("should fail if creating table twice, unless overwrite is true", async () => {
|
||||||
let tbl = await db.createTable("test", [{ id: 1 }, { id: 2 }]);
|
let tbl = await db.createTable("test", [{ id: 1 }, { id: 2 }]);
|
||||||
await expect(tbl.countRows()).resolves.toBe(2);
|
await expect(tbl.countRows()).resolves.toBe(2);
|
||||||
@@ -96,14 +116,15 @@ describe("given a connection", () => {
|
|||||||
const data = [...Array(10000).keys()].map((i) => ({ id: i }));
|
const data = [...Array(10000).keys()].map((i) => ({ id: i }));
|
||||||
|
|
||||||
// Create in v1 mode
|
// Create in v1 mode
|
||||||
let table = await db.createTable("test", data, { useLegacyFormat: true });
|
let table = await db.createTable("test", data, {
|
||||||
|
storageOptions: { newTableDataStorageVersion: "legacy" },
|
||||||
|
});
|
||||||
|
|
||||||
const isV2 = async (table: Table) => {
|
const isV2 = async (table: Table) => {
|
||||||
const data = await table
|
const data = await table
|
||||||
.query()
|
.query()
|
||||||
.limit(10000)
|
.limit(10000)
|
||||||
.toArrow({ maxBatchLength: 100000 });
|
.toArrow({ maxBatchLength: 100000 });
|
||||||
console.log(data.batches.length);
|
|
||||||
return data.batches.length < 5;
|
return data.batches.length < 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +143,7 @@ describe("given a connection", () => {
|
|||||||
const schema = new Schema([new Field("id", new Float64(), true)]);
|
const schema = new Schema([new Field("id", new Float64(), true)]);
|
||||||
|
|
||||||
table = await db.createEmptyTable("test_v2_empty", schema, {
|
table = await db.createEmptyTable("test_v2_empty", schema, {
|
||||||
useLegacyFormat: false,
|
storageOptions: { newTableDataStorageVersion: "stable" },
|
||||||
});
|
});
|
||||||
|
|
||||||
await table.add(data);
|
await table.add(data);
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
import { EmbeddingFunction, LanceSchema } from "../lancedb/embedding";
|
import { EmbeddingFunction, LanceSchema } from "../lancedb/embedding";
|
||||||
import { getRegistry, register } from "../lancedb/embedding/registry";
|
import { getRegistry, register } from "../lancedb/embedding/registry";
|
||||||
|
|
||||||
|
const testOpenAIInteg = process.env.OPENAI_API_KEY == null ? test.skip : test;
|
||||||
|
|
||||||
describe("embedding functions", () => {
|
describe("embedding functions", () => {
|
||||||
let tmpDir: tmp.DirResult;
|
let tmpDir: tmp.DirResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -29,9 +31,6 @@ describe("embedding functions", () => {
|
|||||||
|
|
||||||
it("should be able to create a table with an embedding function", async () => {
|
it("should be able to create a table with an embedding function", async () => {
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
@@ -75,9 +74,6 @@ describe("embedding functions", () => {
|
|||||||
it("should be able to append and upsert using embedding function", async () => {
|
it("should be able to append and upsert using embedding function", async () => {
|
||||||
@register()
|
@register()
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
@@ -143,9 +139,6 @@ describe("embedding functions", () => {
|
|||||||
it("should be able to create an empty table with an embedding function", async () => {
|
it("should be able to create an empty table with an embedding function", async () => {
|
||||||
@register()
|
@register()
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
@@ -194,9 +187,6 @@ describe("embedding functions", () => {
|
|||||||
it("should error when appending to a table with an unregistered embedding function", async () => {
|
it("should error when appending to a table with an unregistered embedding function", async () => {
|
||||||
@register("mock")
|
@register("mock")
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
@@ -241,13 +231,35 @@ describe("embedding functions", () => {
|
|||||||
`Function "mock" not found in registry`,
|
`Function "mock" not found in registry`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testOpenAIInteg("propagates variables through all methods", async () => {
|
||||||
|
delete process.env.OPENAI_API_KEY;
|
||||||
|
const registry = getRegistry();
|
||||||
|
registry.setVar("openai_api_key", "sk-...");
|
||||||
|
const func = registry.get("openai")?.create({
|
||||||
|
model: "text-embedding-ada-002",
|
||||||
|
apiKey: "$var:openai_api_key",
|
||||||
|
}) as EmbeddingFunction;
|
||||||
|
|
||||||
|
const db = await connect("memory://");
|
||||||
|
const wordsSchema = LanceSchema({
|
||||||
|
text: func.sourceField(new Utf8()),
|
||||||
|
vector: func.vectorField(),
|
||||||
|
});
|
||||||
|
const tbl = await db.createEmptyTable("words", wordsSchema, {
|
||||||
|
mode: "overwrite",
|
||||||
|
});
|
||||||
|
await tbl.add([{ text: "hello world" }, { text: "goodbye world" }]);
|
||||||
|
|
||||||
|
const query = "greetings";
|
||||||
|
const actual = (await tbl.search(query).limit(1).toArray())[0];
|
||||||
|
expect(actual).toHaveProperty("text");
|
||||||
|
});
|
||||||
|
|
||||||
test.each([new Float16(), new Float32(), new Float64()])(
|
test.each([new Float16(), new Float32(), new Float64()])(
|
||||||
"should be able to provide manual embeddings with multiple float datatype",
|
"should be able to provide manual embeddings with multiple float datatype",
|
||||||
async (floatType) => {
|
async (floatType) => {
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
@@ -292,10 +304,6 @@ describe("embedding functions", () => {
|
|||||||
async (floatType) => {
|
async (floatType) => {
|
||||||
@register("test1")
|
@register("test1")
|
||||||
class MockEmbeddingFunctionWithoutNDims extends EmbeddingFunction<string> {
|
class MockEmbeddingFunctionWithoutNDims extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddingDataType(): Float {
|
embeddingDataType(): Float {
|
||||||
return floatType;
|
return floatType;
|
||||||
}
|
}
|
||||||
@@ -310,9 +318,6 @@ describe("embedding functions", () => {
|
|||||||
}
|
}
|
||||||
@register("test")
|
@register("test")
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import * as arrow18 from "apache-arrow-18";
|
|||||||
import * as tmp from "tmp";
|
import * as tmp from "tmp";
|
||||||
|
|
||||||
import { connect } from "../lancedb";
|
import { connect } from "../lancedb";
|
||||||
import { EmbeddingFunction, LanceSchema } from "../lancedb/embedding";
|
import {
|
||||||
|
EmbeddingFunction,
|
||||||
|
FunctionOptions,
|
||||||
|
LanceSchema,
|
||||||
|
} from "../lancedb/embedding";
|
||||||
import { getRegistry, register } from "../lancedb/embedding/registry";
|
import { getRegistry, register } from "../lancedb/embedding/registry";
|
||||||
|
|
||||||
describe.each([arrow15, arrow16, arrow17, arrow18])("LanceSchema", (arrow) => {
|
describe.each([arrow15, arrow16, arrow17, arrow18])("LanceSchema", (arrow) => {
|
||||||
@@ -39,11 +43,6 @@ describe.each([arrow15, arrow16, arrow17, arrow18])("Registry", (arrow) => {
|
|||||||
it("should register a new item to the registry", async () => {
|
it("should register a new item to the registry", async () => {
|
||||||
@register("mock-embedding")
|
@register("mock-embedding")
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {
|
|
||||||
someText: "hello",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -89,11 +88,6 @@ describe.each([arrow15, arrow16, arrow17, arrow18])("Registry", (arrow) => {
|
|||||||
});
|
});
|
||||||
test("should error if registering with the same name", async () => {
|
test("should error if registering with the same name", async () => {
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {
|
|
||||||
someText: "hello",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -114,13 +108,9 @@ describe.each([arrow15, arrow16, arrow17, arrow18])("Registry", (arrow) => {
|
|||||||
});
|
});
|
||||||
test("schema should contain correct metadata", async () => {
|
test("schema should contain correct metadata", async () => {
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
constructor(args: FunctionOptions = {}) {
|
||||||
return {
|
|
||||||
someText: "hello",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
constructor() {
|
|
||||||
super();
|
super();
|
||||||
|
this.resolveVariables(args);
|
||||||
}
|
}
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
@@ -132,7 +122,7 @@ describe.each([arrow15, arrow16, arrow17, arrow18])("Registry", (arrow) => {
|
|||||||
return data.map(() => [1, 2, 3]);
|
return data.map(() => [1, 2, 3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const func = new MockEmbeddingFunction();
|
const func = new MockEmbeddingFunction({ someText: "hello" });
|
||||||
|
|
||||||
const schema = LanceSchema({
|
const schema = LanceSchema({
|
||||||
id: new arrow.Int32(),
|
id: new arrow.Int32(),
|
||||||
@@ -155,3 +145,79 @@ describe.each([arrow15, arrow16, arrow17, arrow18])("Registry", (arrow) => {
|
|||||||
expect(schema.metadata).toEqual(expectedMetadata);
|
expect(schema.metadata).toEqual(expectedMetadata);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Registry.setVar", () => {
|
||||||
|
const registry = getRegistry();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
@register("mock-embedding")
|
||||||
|
// biome-ignore lint/correctness/noUnusedVariables :
|
||||||
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
|
constructor(optionsRaw: FunctionOptions = {}) {
|
||||||
|
super();
|
||||||
|
const options = this.resolveVariables(optionsRaw);
|
||||||
|
|
||||||
|
expect(optionsRaw["someKey"].startsWith("$var:someName")).toBe(true);
|
||||||
|
expect(options["someKey"]).toBe("someValue");
|
||||||
|
|
||||||
|
if (options["secretKey"]) {
|
||||||
|
expect(optionsRaw["secretKey"]).toBe("$var:secretKey");
|
||||||
|
expect(options["secretKey"]).toBe("mySecret");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(data: string[]) {
|
||||||
|
return data.map(() => [1, 2, 3]);
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new arrow18.Float32() as apiArrow.Float;
|
||||||
|
}
|
||||||
|
protected getSensitiveKeys() {
|
||||||
|
return ["secretKey"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
registry.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should error if the variable is not set", () => {
|
||||||
|
console.log(registry.get("mock-embedding"));
|
||||||
|
expect(() =>
|
||||||
|
registry.get("mock-embedding")!.create({ someKey: "$var:someName" }),
|
||||||
|
).toThrow('Variable "someName" not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use default values if not set", () => {
|
||||||
|
registry
|
||||||
|
.get("mock-embedding")!
|
||||||
|
.create({ someKey: "$var:someName:someValue" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set a variable that the embedding function understand", () => {
|
||||||
|
registry.setVar("someName", "someValue");
|
||||||
|
registry.get("mock-embedding")!.create({ someKey: "$var:someName" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject secrets that aren't passed as variables", () => {
|
||||||
|
registry.setVar("someName", "someValue");
|
||||||
|
expect(() =>
|
||||||
|
registry
|
||||||
|
.get("mock-embedding")!
|
||||||
|
.create({ secretKey: "someValue", someKey: "$var:someName" }),
|
||||||
|
).toThrow(
|
||||||
|
'The key "secretKey" is sensitive and cannot be set directly. Please use the $var: syntax to set it.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not serialize secrets", () => {
|
||||||
|
registry.setVar("someName", "someValue");
|
||||||
|
registry.setVar("secretKey", "mySecret");
|
||||||
|
const func = registry
|
||||||
|
.get("mock-embedding")!
|
||||||
|
.create({ secretKey: "$var:secretKey", someKey: "$var:someName" });
|
||||||
|
expect(func.toJSON()).toEqual({
|
||||||
|
secretKey: "$var:secretKey",
|
||||||
|
someKey: "$var:someName",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -104,4 +104,26 @@ describe("remote connection", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should pass on requested extra headers", async () => {
|
||||||
|
await withMockDatabase(
|
||||||
|
(req, res) => {
|
||||||
|
expect(req.headers["x-my-header"]).toEqual("my-value");
|
||||||
|
|
||||||
|
const body = JSON.stringify({ tables: [] });
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" }).end(body);
|
||||||
|
},
|
||||||
|
async (db) => {
|
||||||
|
const tableNames = await db.tableNames();
|
||||||
|
expect(tableNames).toEqual([]);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientConfig: {
|
||||||
|
extraHeaders: {
|
||||||
|
"x-my-header": "my-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ maybeDescribe("storage_options", () => {
|
|||||||
|
|
||||||
tableNames = await db.tableNames();
|
tableNames = await db.tableNames();
|
||||||
expect(tableNames).toEqual([]);
|
expect(tableNames).toEqual([]);
|
||||||
|
|
||||||
|
await db.dropAllTables();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can configure encryption at connection and table level", async () => {
|
it("can configure encryption at connection and table level", async () => {
|
||||||
@@ -210,6 +212,8 @@ maybeDescribe("storage_options", () => {
|
|||||||
await table.add([{ a: 2, b: 3 }]);
|
await table.add([{ a: 2, b: 3 }]);
|
||||||
|
|
||||||
await bucket.assertAllEncrypted("test/table2.lance", kmsKey.keyId);
|
await bucket.assertAllEncrypted("test/table2.lance", kmsKey.keyId);
|
||||||
|
|
||||||
|
await db.dropAllTables();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -298,5 +302,32 @@ maybeDescribe("DynamoDB Lock", () => {
|
|||||||
|
|
||||||
const rowCount = await table.countRows();
|
const rowCount = await table.countRows();
|
||||||
expect(rowCount).toBe(6);
|
expect(rowCount).toBe(6);
|
||||||
|
|
||||||
|
await db.dropAllTables();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears dynamodb state after dropping all tables", async () => {
|
||||||
|
const uri = `s3+ddb://${bucket.name}/test?ddbTableName=${commitTable.name}`;
|
||||||
|
const db = await connect(uri, {
|
||||||
|
storageOptions: CONFIG,
|
||||||
|
readConsistencyInterval: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.createTable("foo", [{ a: 1, b: 2 }]);
|
||||||
|
await db.createTable("bar", [{ a: 1, b: 2 }]);
|
||||||
|
|
||||||
|
let tableNames = await db.tableNames();
|
||||||
|
expect(tableNames).toEqual(["bar", "foo"]);
|
||||||
|
|
||||||
|
await db.dropAllTables();
|
||||||
|
tableNames = await db.tableNames();
|
||||||
|
expect(tableNames).toEqual([]);
|
||||||
|
|
||||||
|
// We can create a new table with the same name as the one we dropped.
|
||||||
|
await db.createTable("foo", [{ a: 1, b: 2 }]);
|
||||||
|
tableNames = await db.tableNames();
|
||||||
|
expect(tableNames).toEqual(["foo"]);
|
||||||
|
|
||||||
|
await db.dropAllTables();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -253,6 +253,31 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
const arrowTbl = await table.toArrow();
|
const arrowTbl = await table.toArrow();
|
||||||
expect(arrowTbl).toBeInstanceOf(ArrowTable);
|
expect(arrowTbl).toBeInstanceOf(ArrowTable);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should be able to handle missing fields", async () => {
|
||||||
|
const schema = new arrow.Schema([
|
||||||
|
new arrow.Field("id", new arrow.Int32(), true),
|
||||||
|
new arrow.Field("y", new arrow.Int32(), true),
|
||||||
|
new arrow.Field("z", new arrow.Int64(), true),
|
||||||
|
]);
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const table = await db.createEmptyTable("testNull", schema);
|
||||||
|
await table.add([{ id: 1, y: 2 }]);
|
||||||
|
await table.add([{ id: 2 }]);
|
||||||
|
|
||||||
|
await table
|
||||||
|
.mergeInsert("id")
|
||||||
|
.whenNotMatchedInsertAll()
|
||||||
|
.execute([
|
||||||
|
{ id: 3, z: 3 },
|
||||||
|
{ id: 4, z: 5 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const res = await table.query().toArrow();
|
||||||
|
expect(res.getChild("id")?.toJSON()).toEqual([1, 2, 3, 4]);
|
||||||
|
expect(res.getChild("y")?.toJSON()).toEqual([2, null, null, null]);
|
||||||
|
expect(res.getChild("z")?.toJSON()).toEqual([null, null, 3n, 5n]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -641,11 +666,11 @@ describe("When creating an index", () => {
|
|||||||
expect(fs.readdirSync(indexDir)).toHaveLength(1);
|
expect(fs.readdirSync(indexDir)).toHaveLength(1);
|
||||||
|
|
||||||
for await (const r of tbl.query().where("id > 1").select(["id"])) {
|
for await (const r of tbl.query().where("id > 1").select(["id"])) {
|
||||||
expect(r.numRows).toBe(10);
|
expect(r.numRows).toBe(298);
|
||||||
}
|
}
|
||||||
// should also work with 'filter' alias
|
// should also work with 'filter' alias
|
||||||
for await (const r of tbl.query().filter("id > 1").select(["id"])) {
|
for await (const r of tbl.query().filter("id > 1").select(["id"])) {
|
||||||
expect(r.numRows).toBe(10);
|
expect(r.numRows).toBe(298);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1013,9 +1038,6 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
test("can search using a string", async () => {
|
test("can search using a string", async () => {
|
||||||
@register()
|
@register()
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
ndims() {
|
ndims() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,17 @@ test("custom embedding function", async () => {
|
|||||||
|
|
||||||
@register("my_embedding")
|
@register("my_embedding")
|
||||||
class MyEmbeddingFunction extends EmbeddingFunction<string> {
|
class MyEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
toJSON(): object {
|
constructor(optionsRaw = {}) {
|
||||||
return {};
|
super();
|
||||||
|
const options = this.resolveVariables(optionsRaw);
|
||||||
|
// Initialize using options
|
||||||
}
|
}
|
||||||
ndims() {
|
ndims() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
protected getSensitiveKeys(): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
embeddingDataType(): Float {
|
embeddingDataType(): Float {
|
||||||
return new Float32();
|
return new Float32();
|
||||||
}
|
}
|
||||||
@@ -94,3 +99,14 @@ test("custom embedding function", async () => {
|
|||||||
expect(await table2.countRows()).toBe(2);
|
expect(await table2.countRows()).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("embedding function api_key", async () => {
|
||||||
|
// --8<-- [start:register_secret]
|
||||||
|
const registry = getRegistry();
|
||||||
|
registry.setVar("api_key", "sk-...");
|
||||||
|
|
||||||
|
const func = registry.get("openai")!.create({
|
||||||
|
apiKey: "$var:api_key",
|
||||||
|
});
|
||||||
|
// --8<-- [end:register_secret]
|
||||||
|
});
|
||||||
|
|||||||
@@ -42,4 +42,4 @@ test("full text search", async () => {
|
|||||||
expect(result.length).toBe(10);
|
expect(result.length).toBe(10);
|
||||||
// --8<-- [end:full_text_search]
|
// --8<-- [end:full_text_search]
|
||||||
});
|
});
|
||||||
});
|
}, 10_000);
|
||||||
|
|||||||
@@ -2,31 +2,37 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Data as ArrowData,
|
||||||
Table as ArrowTable,
|
Table as ArrowTable,
|
||||||
Binary,
|
Binary,
|
||||||
|
Bool,
|
||||||
BufferType,
|
BufferType,
|
||||||
DataType,
|
DataType,
|
||||||
|
Dictionary,
|
||||||
Field,
|
Field,
|
||||||
FixedSizeBinary,
|
FixedSizeBinary,
|
||||||
FixedSizeList,
|
FixedSizeList,
|
||||||
Float,
|
Float,
|
||||||
Float32,
|
Float32,
|
||||||
|
Float64,
|
||||||
Int,
|
Int,
|
||||||
|
Int32,
|
||||||
|
Int64,
|
||||||
LargeBinary,
|
LargeBinary,
|
||||||
List,
|
List,
|
||||||
Null,
|
Null,
|
||||||
RecordBatch,
|
RecordBatch,
|
||||||
RecordBatchFileReader,
|
RecordBatchFileReader,
|
||||||
RecordBatchFileWriter,
|
RecordBatchFileWriter,
|
||||||
RecordBatchReader,
|
|
||||||
RecordBatchStreamWriter,
|
RecordBatchStreamWriter,
|
||||||
Schema,
|
Schema,
|
||||||
Struct,
|
Struct,
|
||||||
Utf8,
|
Utf8,
|
||||||
Vector,
|
Vector,
|
||||||
|
makeVector as arrowMakeVector,
|
||||||
makeBuilder,
|
makeBuilder,
|
||||||
makeData,
|
makeData,
|
||||||
type makeTable,
|
makeTable,
|
||||||
vectorFromArray,
|
vectorFromArray,
|
||||||
} from "apache-arrow";
|
} from "apache-arrow";
|
||||||
import { Buffers } from "apache-arrow/data";
|
import { Buffers } from "apache-arrow/data";
|
||||||
@@ -236,8 +242,6 @@ export class MakeArrowTableOptions {
|
|||||||
* This function converts an array of Record<String, any> (row-major JS objects)
|
* This function converts an array of Record<String, any> (row-major JS objects)
|
||||||
* to an Arrow Table (a columnar structure)
|
* to an Arrow Table (a columnar structure)
|
||||||
*
|
*
|
||||||
* Note that it currently does not support nulls.
|
|
||||||
*
|
|
||||||
* If a schema is provided then it will be used to determine the resulting array
|
* If a schema is provided then it will be used to determine the resulting array
|
||||||
* types. Fields will also be reordered to fit the order defined by the schema.
|
* types. Fields will also be reordered to fit the order defined by the schema.
|
||||||
*
|
*
|
||||||
@@ -245,6 +249,9 @@ export class MakeArrowTableOptions {
|
|||||||
* will be controlled by the order of properties in the first record. If a type
|
* will be controlled by the order of properties in the first record. If a type
|
||||||
* is inferred it will always be nullable.
|
* is inferred it will always be nullable.
|
||||||
*
|
*
|
||||||
|
* If not all fields are found in the data, then a subset of the schema will be
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
* If the input is empty then a schema must be provided to create an empty table.
|
* If the input is empty then a schema must be provided to create an empty table.
|
||||||
*
|
*
|
||||||
* When a schema is not specified then data types will be inferred. The inference
|
* When a schema is not specified then data types will be inferred. The inference
|
||||||
@@ -252,6 +259,7 @@ export class MakeArrowTableOptions {
|
|||||||
*
|
*
|
||||||
* - boolean => Bool
|
* - boolean => Bool
|
||||||
* - number => Float64
|
* - number => Float64
|
||||||
|
* - bigint => Int64
|
||||||
* - String => Utf8
|
* - String => Utf8
|
||||||
* - Buffer => Binary
|
* - Buffer => Binary
|
||||||
* - Record<String, any> => Struct
|
* - Record<String, any> => Struct
|
||||||
@@ -322,126 +330,316 @@ export function makeArrowTable(
|
|||||||
options?: Partial<MakeArrowTableOptions>,
|
options?: Partial<MakeArrowTableOptions>,
|
||||||
metadata?: Map<string, string>,
|
metadata?: Map<string, string>,
|
||||||
): ArrowTable {
|
): ArrowTable {
|
||||||
|
const opt = new MakeArrowTableOptions(options !== undefined ? options : {});
|
||||||
|
let schema: Schema | undefined = undefined;
|
||||||
|
if (opt.schema !== undefined && opt.schema !== null) {
|
||||||
|
schema = sanitizeSchema(opt.schema);
|
||||||
|
schema = validateSchemaEmbeddings(
|
||||||
|
schema as Schema,
|
||||||
|
data,
|
||||||
|
options?.embeddingFunction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let schemaMetadata = schema?.metadata || new Map<string, string>();
|
||||||
|
if (metadata !== undefined) {
|
||||||
|
schemaMetadata = new Map([...schemaMetadata, ...metadata]);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
data.length === 0 &&
|
data.length === 0 &&
|
||||||
(options?.schema === undefined || options?.schema === null)
|
(options?.schema === undefined || options?.schema === null)
|
||||||
) {
|
) {
|
||||||
throw new Error("At least one record or a schema needs to be provided");
|
throw new Error("At least one record or a schema needs to be provided");
|
||||||
}
|
} else if (data.length === 0) {
|
||||||
|
if (schema === undefined) {
|
||||||
const opt = new MakeArrowTableOptions(options !== undefined ? options : {});
|
throw new Error("A schema must be provided if data is empty");
|
||||||
if (opt.schema !== undefined && opt.schema !== null) {
|
|
||||||
opt.schema = sanitizeSchema(opt.schema);
|
|
||||||
opt.schema = validateSchemaEmbeddings(
|
|
||||||
opt.schema as Schema,
|
|
||||||
data,
|
|
||||||
options?.embeddingFunction,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const columns: Record<string, Vector> = {};
|
|
||||||
// TODO: sample dataset to find missing columns
|
|
||||||
// Prefer the field ordering of the schema, if present
|
|
||||||
const columnNames =
|
|
||||||
opt.schema != null ? (opt.schema.names as string[]) : Object.keys(data[0]);
|
|
||||||
for (const colName of columnNames) {
|
|
||||||
if (
|
|
||||||
data.length !== 0 &&
|
|
||||||
!Object.prototype.hasOwnProperty.call(data[0], colName)
|
|
||||||
) {
|
|
||||||
// The field is present in the schema, but not in the data, skip it
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Extract a single column from the records (transpose from row-major to col-major)
|
|
||||||
let values = data.map((datum) => datum[colName]);
|
|
||||||
|
|
||||||
// By default (type === undefined) arrow will infer the type from the JS type
|
|
||||||
let type;
|
|
||||||
if (opt.schema !== undefined) {
|
|
||||||
// If there is a schema provided, then use that for the type instead
|
|
||||||
type = opt.schema?.fields.filter((f) => f.name === colName)[0]?.type;
|
|
||||||
if (DataType.isInt(type) && type.bitWidth === 64) {
|
|
||||||
// wrap in BigInt to avoid bug: https://github.com/apache/arrow/issues/40051
|
|
||||||
values = values.map((v) => {
|
|
||||||
if (v === null) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
if (typeof v === "bigint") {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
if (typeof v === "number") {
|
|
||||||
return BigInt(v);
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
`Expected BigInt or number for column ${colName}, got ${typeof v}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, check to see if this column is one of the vector columns
|
schema = new Schema(schema.fields, schemaMetadata);
|
||||||
// defined by opt.vectorColumns and, if so, use the fixed size list type
|
return new ArrowTable(schema);
|
||||||
const vectorColumnOptions = opt.vectorColumns[colName];
|
}
|
||||||
if (vectorColumnOptions !== undefined) {
|
}
|
||||||
const firstNonNullValue = values.find((v) => v !== null);
|
|
||||||
if (Array.isArray(firstNonNullValue)) {
|
let inferredSchema = inferSchema(data, schema, opt);
|
||||||
type = newVectorType(
|
inferredSchema = new Schema(inferredSchema.fields, schemaMetadata);
|
||||||
firstNonNullValue.length,
|
|
||||||
vectorColumnOptions.type,
|
const finalColumns: Record<string, Vector> = {};
|
||||||
);
|
for (const field of inferredSchema.fields) {
|
||||||
|
finalColumns[field.name] = transposeData(data, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrowTable(inferredSchema, finalColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferSchema(
|
||||||
|
data: Array<Record<string, unknown>>,
|
||||||
|
schema: Schema | undefined,
|
||||||
|
opts: MakeArrowTableOptions,
|
||||||
|
): Schema {
|
||||||
|
// We will collect all fields we see in the data.
|
||||||
|
const pathTree = new PathTree<DataType>();
|
||||||
|
|
||||||
|
for (const [rowI, row] of data.entries()) {
|
||||||
|
for (const [path, value] of rowPathsAndValues(row)) {
|
||||||
|
if (!pathTree.has(path)) {
|
||||||
|
// First time seeing this field.
|
||||||
|
if (schema !== undefined) {
|
||||||
|
const field = getFieldForPath(schema, path);
|
||||||
|
if (field === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Found field not in schema: ${path.join(".")} at row ${rowI}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
pathTree.set(path, field.type);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
const inferredType = inferType(value, path, opts);
|
||||||
`Column ${colName} is expected to be a vector column but first non-null value is not an array. Could not determine size of vector column`,
|
if (inferredType === undefined) {
|
||||||
);
|
throw new Error(`Failed to infer data type for field ${path.join(".")} at row ${rowI}. \
|
||||||
|
Consider providing an explicit schema.`);
|
||||||
|
}
|
||||||
|
pathTree.set(path, inferredType);
|
||||||
|
}
|
||||||
|
} else if (schema === undefined) {
|
||||||
|
const currentType = pathTree.get(path);
|
||||||
|
const newType = inferType(value, path, opts);
|
||||||
|
if (currentType !== newType) {
|
||||||
|
new Error(`Failed to infer schema for data. Previously inferred type \
|
||||||
|
${currentType} but found ${newType} at row ${rowI}. Consider \
|
||||||
|
providing an explicit schema.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
// Convert an Array of JS values to an arrow vector
|
|
||||||
columns[colName] = makeVector(values, type, opt.dictionaryEncodeStrings);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
||||||
throw Error(`Could not convert column "${colName}" to Arrow: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opt.schema != null) {
|
if (schema === undefined) {
|
||||||
// `new ArrowTable(columns)` infers a schema which may sometimes have
|
function fieldsFromPathTree(pathTree: PathTree<DataType>): Field[] {
|
||||||
// incorrect nullability (it assumes nullable=true always)
|
const fields = [];
|
||||||
//
|
for (const [name, value] of pathTree.map.entries()) {
|
||||||
// `new ArrowTable(schema, columns)` will also fail because it will create a
|
if (value instanceof PathTree) {
|
||||||
// batch with an inferred schema and then complain that the batch schema
|
const children = fieldsFromPathTree(value);
|
||||||
// does not match the provided schema.
|
fields.push(new Field(name, new Struct(children), true));
|
||||||
//
|
} else {
|
||||||
// To work around this we first create a table with the wrong schema and
|
fields.push(new Field(name, value, true));
|
||||||
// then patch the schema of the batches so we can use
|
|
||||||
// `new ArrowTable(schema, batches)` which does not do any schema inference
|
|
||||||
const firstTable = new ArrowTable(columns);
|
|
||||||
const batchesFixed = firstTable.batches.map(
|
|
||||||
(batch) => new RecordBatch(opt.schema as Schema, batch.data),
|
|
||||||
);
|
|
||||||
let schema: Schema;
|
|
||||||
if (metadata !== undefined) {
|
|
||||||
let schemaMetadata = opt.schema.metadata;
|
|
||||||
if (schemaMetadata.size === 0) {
|
|
||||||
schemaMetadata = metadata;
|
|
||||||
} else {
|
|
||||||
for (const [key, entry] of schemaMetadata.entries()) {
|
|
||||||
schemaMetadata.set(key, entry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
const fields = fieldsFromPathTree(pathTree);
|
||||||
|
return new Schema(fields);
|
||||||
|
} else {
|
||||||
|
function takeMatchingFields(
|
||||||
|
fields: Field[],
|
||||||
|
pathTree: PathTree<DataType>,
|
||||||
|
): Field[] {
|
||||||
|
const outFields = [];
|
||||||
|
for (const field of fields) {
|
||||||
|
if (pathTree.map.has(field.name)) {
|
||||||
|
const value = pathTree.get([field.name]);
|
||||||
|
if (value instanceof PathTree) {
|
||||||
|
const struct = field.type as Struct;
|
||||||
|
const children = takeMatchingFields(struct.children, value);
|
||||||
|
outFields.push(
|
||||||
|
new Field(field.name, new Struct(children), field.nullable),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
outFields.push(
|
||||||
|
new Field(field.name, value as DataType, field.nullable),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outFields;
|
||||||
|
}
|
||||||
|
const fields = takeMatchingFields(schema.fields, pathTree);
|
||||||
|
return new Schema(fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
schema = new Schema(opt.schema.fields as Field[], schemaMetadata);
|
function* rowPathsAndValues(
|
||||||
|
row: Record<string, unknown>,
|
||||||
|
basePath: string[] = [],
|
||||||
|
): Generator<[string[], unknown]> {
|
||||||
|
for (const [key, value] of Object.entries(row)) {
|
||||||
|
if (isObject(value)) {
|
||||||
|
yield* rowPathsAndValues(value, [...basePath, key]);
|
||||||
} else {
|
} else {
|
||||||
schema = opt.schema as Schema;
|
yield [[...basePath, key], value];
|
||||||
}
|
}
|
||||||
return new ArrowTable(schema, batchesFixed);
|
|
||||||
}
|
}
|
||||||
const tbl = new ArrowTable(columns);
|
}
|
||||||
if (metadata !== undefined) {
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
(<any>tbl.schema).metadata = metadata;
|
return (
|
||||||
|
typeof value === "object" &&
|
||||||
|
value !== null &&
|
||||||
|
!Array.isArray(value) &&
|
||||||
|
!(value instanceof RegExp) &&
|
||||||
|
!(value instanceof Date) &&
|
||||||
|
!(value instanceof Set) &&
|
||||||
|
!(value instanceof Map) &&
|
||||||
|
!(value instanceof Buffer)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFieldForPath(schema: Schema, path: string[]): Field | undefined {
|
||||||
|
let current: Field | Schema = schema;
|
||||||
|
for (const key of path) {
|
||||||
|
if (current instanceof Schema) {
|
||||||
|
const field: Field | undefined = current.fields.find(
|
||||||
|
(f) => f.name === key,
|
||||||
|
);
|
||||||
|
if (field === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = field;
|
||||||
|
} else if (current instanceof Field && DataType.isStruct(current.type)) {
|
||||||
|
const struct: Struct = current.type;
|
||||||
|
const field = struct.children.find((f) => f.name === key);
|
||||||
|
if (field === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = field;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current instanceof Field) {
|
||||||
|
return current;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to infer which Arrow type to use for a given value.
|
||||||
|
*
|
||||||
|
* May return undefined if the type cannot be inferred.
|
||||||
|
*/
|
||||||
|
function inferType(
|
||||||
|
value: unknown,
|
||||||
|
path: string[],
|
||||||
|
opts: MakeArrowTableOptions,
|
||||||
|
): DataType | undefined {
|
||||||
|
if (typeof value === "bigint") {
|
||||||
|
return new Int64();
|
||||||
|
} else if (typeof value === "number") {
|
||||||
|
// Even if it's an integer, it's safer to assume Float64. Users can
|
||||||
|
// always provide an explicit schema or use BigInt if they mean integer.
|
||||||
|
return new Float64();
|
||||||
|
} else if (typeof value === "string") {
|
||||||
|
if (opts.dictionaryEncodeStrings) {
|
||||||
|
return new Dictionary(new Utf8(), new Int32());
|
||||||
|
} else {
|
||||||
|
return new Utf8();
|
||||||
|
}
|
||||||
|
} else if (typeof value === "boolean") {
|
||||||
|
return new Bool();
|
||||||
|
} else if (value instanceof Buffer) {
|
||||||
|
return new Binary();
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
if (value.length === 0) {
|
||||||
|
return undefined; // Without any values we can't infer the type
|
||||||
|
}
|
||||||
|
if (path.length === 1 && Object.hasOwn(opts.vectorColumns, path[0])) {
|
||||||
|
const floatType = sanitizeType(opts.vectorColumns[path[0]].type);
|
||||||
|
return new FixedSizeList(
|
||||||
|
value.length,
|
||||||
|
new Field("item", floatType, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const valueType = inferType(value[0], path, opts);
|
||||||
|
if (valueType === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Try to automatically detect embedding columns.
|
||||||
|
if (valueType instanceof Float && path[path.length - 1] === "vector") {
|
||||||
|
// We default to Float32 for vectors.
|
||||||
|
const child = new Field("item", new Float32(), true);
|
||||||
|
return new FixedSizeList(value.length, child);
|
||||||
|
} else {
|
||||||
|
const child = new Field("item", valueType, true);
|
||||||
|
return new List(child);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: timestamp
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathTree<V> {
|
||||||
|
map: Map<string, V | PathTree<V>>;
|
||||||
|
|
||||||
|
constructor(entries?: [string[], V][]) {
|
||||||
|
this.map = new Map();
|
||||||
|
if (entries !== undefined) {
|
||||||
|
for (const [path, value] of entries) {
|
||||||
|
this.set(path, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has(path: string[]): boolean {
|
||||||
|
let ref: PathTree<V> = this;
|
||||||
|
for (const part of path) {
|
||||||
|
if (!(ref instanceof PathTree) || !ref.map.has(part)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ref = ref.map.get(part) as PathTree<V>;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
get(path: string[]): V | undefined {
|
||||||
|
let ref: PathTree<V> = this;
|
||||||
|
for (const part of path) {
|
||||||
|
if (!(ref instanceof PathTree) || !ref.map.has(part)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
ref = ref.map.get(part) as PathTree<V>;
|
||||||
|
}
|
||||||
|
return ref as V;
|
||||||
|
}
|
||||||
|
set(path: string[], value: V): void {
|
||||||
|
let ref: PathTree<V> = this;
|
||||||
|
for (const part of path.slice(0, path.length - 1)) {
|
||||||
|
if (!ref.map.has(part)) {
|
||||||
|
ref.map.set(part, new PathTree<V>());
|
||||||
|
}
|
||||||
|
ref = ref.map.get(part) as PathTree<V>;
|
||||||
|
}
|
||||||
|
ref.map.set(path[path.length - 1], value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transposeData(
|
||||||
|
data: Record<string, unknown>[],
|
||||||
|
field: Field,
|
||||||
|
path: string[] = [],
|
||||||
|
): Vector {
|
||||||
|
if (field.type instanceof Struct) {
|
||||||
|
const childFields = field.type.children;
|
||||||
|
const childVectors = childFields.map((child) => {
|
||||||
|
return transposeData(data, child, [...path, child.name]);
|
||||||
|
});
|
||||||
|
const structData = makeData({
|
||||||
|
type: field.type,
|
||||||
|
children: childVectors as unknown as ArrowData<DataType>[],
|
||||||
|
});
|
||||||
|
return arrowMakeVector(structData);
|
||||||
|
} else {
|
||||||
|
const valuesPath = [...path, field.name];
|
||||||
|
const values = data.map((datum) => {
|
||||||
|
let current: unknown = datum;
|
||||||
|
for (const key of valuesPath) {
|
||||||
|
if (isObject(current) && Object.hasOwn(current, key)) {
|
||||||
|
current = current[key];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
return makeVector(values, field.type);
|
||||||
}
|
}
|
||||||
return tbl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -491,6 +689,31 @@ function makeVector(
|
|||||||
): Vector<any> {
|
): Vector<any> {
|
||||||
if (type !== undefined) {
|
if (type !== undefined) {
|
||||||
// No need for inference, let Arrow create it
|
// No need for inference, let Arrow create it
|
||||||
|
if (type instanceof Int) {
|
||||||
|
if (DataType.isInt(type) && type.bitWidth === 64) {
|
||||||
|
// wrap in BigInt to avoid bug: https://github.com/apache/arrow/issues/40051
|
||||||
|
values = values.map((v) => {
|
||||||
|
if (v === null) {
|
||||||
|
return v;
|
||||||
|
} else if (typeof v === "bigint") {
|
||||||
|
return v;
|
||||||
|
} else if (typeof v === "number") {
|
||||||
|
return BigInt(v);
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Similarly, bigint isn't supported for 16 or 32-bit ints.
|
||||||
|
values = values.map((v) => {
|
||||||
|
if (typeof v == "bigint") {
|
||||||
|
return Number(v);
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return vectorFromArray(values, type);
|
return vectorFromArray(values, type);
|
||||||
}
|
}
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@@ -902,7 +1125,7 @@ function validateSchemaEmbeddings(
|
|||||||
schema: Schema,
|
schema: Schema,
|
||||||
data: Array<Record<string, unknown>>,
|
data: Array<Record<string, unknown>>,
|
||||||
embeddings: EmbeddingFunctionConfig | undefined,
|
embeddings: EmbeddingFunctionConfig | undefined,
|
||||||
) {
|
): Schema {
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const missingEmbeddingFields = [];
|
const missingEmbeddingFields = [];
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export interface CreateTableOptions {
|
|||||||
*
|
*
|
||||||
* The default is `stable`.
|
* The default is `stable`.
|
||||||
* Set to "legacy" to use the old format.
|
* Set to "legacy" to use the old format.
|
||||||
|
*
|
||||||
|
* @deprecated Pass `new_table_data_storage_version` to storageOptions instead.
|
||||||
*/
|
*/
|
||||||
dataStorageVersion?: string;
|
dataStorageVersion?: string;
|
||||||
|
|
||||||
@@ -61,17 +63,11 @@ export interface CreateTableOptions {
|
|||||||
* turning this on will make the dataset unreadable for older versions
|
* turning this on will make the dataset unreadable for older versions
|
||||||
* of LanceDB (prior to 0.10.0). To migrate an existing dataset, instead
|
* of LanceDB (prior to 0.10.0). To migrate an existing dataset, instead
|
||||||
* use the {@link LocalTable#migrateManifestPathsV2} method.
|
* use the {@link LocalTable#migrateManifestPathsV2} method.
|
||||||
|
*
|
||||||
|
* @deprecated Pass `new_table_enable_v2_manifest_paths` to storageOptions instead.
|
||||||
*/
|
*/
|
||||||
enableV2ManifestPaths?: boolean;
|
enableV2ManifestPaths?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* If true then data files will be written with the legacy format
|
|
||||||
*
|
|
||||||
* The default is false.
|
|
||||||
*
|
|
||||||
* Deprecated. Use data storage version instead.
|
|
||||||
*/
|
|
||||||
useLegacyFormat?: boolean;
|
|
||||||
schema?: SchemaLike;
|
schema?: SchemaLike;
|
||||||
embeddingFunction?: EmbeddingFunctionConfig;
|
embeddingFunction?: EmbeddingFunctionConfig;
|
||||||
}
|
}
|
||||||
@@ -215,6 +211,11 @@ export abstract class Connection {
|
|||||||
* @param {string} name The name of the table to drop.
|
* @param {string} name The name of the table to drop.
|
||||||
*/
|
*/
|
||||||
abstract dropTable(name: string): Promise<void>;
|
abstract dropTable(name: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop all tables in the database.
|
||||||
|
*/
|
||||||
|
abstract dropAllTables(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hideconstructor */
|
/** @hideconstructor */
|
||||||
@@ -256,6 +257,28 @@ export class LocalConnection extends Connection {
|
|||||||
return new LocalTable(innerTable);
|
return new LocalTable(innerTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getStorageOptions(
|
||||||
|
options?: Partial<CreateTableOptions>,
|
||||||
|
): Record<string, string> | undefined {
|
||||||
|
if (options?.dataStorageVersion !== undefined) {
|
||||||
|
if (options.storageOptions === undefined) {
|
||||||
|
options.storageOptions = {};
|
||||||
|
}
|
||||||
|
options.storageOptions["newTableDataStorageVersion"] =
|
||||||
|
options.dataStorageVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.enableV2ManifestPaths !== undefined) {
|
||||||
|
if (options.storageOptions === undefined) {
|
||||||
|
options.storageOptions = {};
|
||||||
|
}
|
||||||
|
options.storageOptions["newTableEnableV2ManifestPaths"] =
|
||||||
|
options.enableV2ManifestPaths ? "true" : "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanseStorageOptions(options?.storageOptions);
|
||||||
|
}
|
||||||
|
|
||||||
async createTable(
|
async createTable(
|
||||||
nameOrOptions:
|
nameOrOptions:
|
||||||
| string
|
| string
|
||||||
@@ -272,20 +295,14 @@ export class LocalConnection extends Connection {
|
|||||||
throw new Error("data is required");
|
throw new Error("data is required");
|
||||||
}
|
}
|
||||||
const { buf, mode } = await parseTableData(data, options);
|
const { buf, mode } = await parseTableData(data, options);
|
||||||
let dataStorageVersion = "stable";
|
|
||||||
if (options?.dataStorageVersion !== undefined) {
|
const storageOptions = this.getStorageOptions(options);
|
||||||
dataStorageVersion = options.dataStorageVersion;
|
|
||||||
} else if (options?.useLegacyFormat !== undefined) {
|
|
||||||
dataStorageVersion = options.useLegacyFormat ? "legacy" : "stable";
|
|
||||||
}
|
|
||||||
|
|
||||||
const innerTable = await this.inner.createTable(
|
const innerTable = await this.inner.createTable(
|
||||||
nameOrOptions,
|
nameOrOptions,
|
||||||
buf,
|
buf,
|
||||||
mode,
|
mode,
|
||||||
cleanseStorageOptions(options?.storageOptions),
|
storageOptions,
|
||||||
dataStorageVersion,
|
|
||||||
options?.enableV2ManifestPaths,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return new LocalTable(innerTable);
|
return new LocalTable(innerTable);
|
||||||
@@ -309,22 +326,14 @@ export class LocalConnection extends Connection {
|
|||||||
metadata = registry.getTableMetadata([embeddingFunction]);
|
metadata = registry.getTableMetadata([embeddingFunction]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataStorageVersion = "stable";
|
const storageOptions = this.getStorageOptions(options);
|
||||||
if (options?.dataStorageVersion !== undefined) {
|
|
||||||
dataStorageVersion = options.dataStorageVersion;
|
|
||||||
} else if (options?.useLegacyFormat !== undefined) {
|
|
||||||
dataStorageVersion = options.useLegacyFormat ? "legacy" : "stable";
|
|
||||||
}
|
|
||||||
|
|
||||||
const table = makeEmptyTable(schema, metadata);
|
const table = makeEmptyTable(schema, metadata);
|
||||||
const buf = await fromTableToBuffer(table);
|
const buf = await fromTableToBuffer(table);
|
||||||
const innerTable = await this.inner.createEmptyTable(
|
const innerTable = await this.inner.createEmptyTable(
|
||||||
name,
|
name,
|
||||||
buf,
|
buf,
|
||||||
mode,
|
mode,
|
||||||
cleanseStorageOptions(options?.storageOptions),
|
storageOptions,
|
||||||
dataStorageVersion,
|
|
||||||
options?.enableV2ManifestPaths,
|
|
||||||
);
|
);
|
||||||
return new LocalTable(innerTable);
|
return new LocalTable(innerTable);
|
||||||
}
|
}
|
||||||
@@ -332,6 +341,10 @@ export class LocalConnection extends Connection {
|
|||||||
async dropTable(name: string): Promise<void> {
|
async dropTable(name: string): Promise<void> {
|
||||||
return this.inner.dropTable(name);
|
return this.inner.dropTable(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async dropAllTables(): Promise<void> {
|
||||||
|
return this.inner.dropAllTables();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
newVectorType,
|
newVectorType,
|
||||||
} from "../arrow";
|
} from "../arrow";
|
||||||
import { sanitizeType } from "../sanitize";
|
import { sanitizeType } from "../sanitize";
|
||||||
|
import { getRegistry } from "./registry";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for a given embedding function
|
* Options for a given embedding function
|
||||||
@@ -32,6 +33,22 @@ export interface EmbeddingFunctionConstructor<
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An embedding function that automatically creates vector representation for a given column.
|
* An embedding function that automatically creates vector representation for a given column.
|
||||||
|
*
|
||||||
|
* It's important subclasses pass the **original** options to the super constructor
|
||||||
|
* and then pass those options to `resolveVariables` to resolve any variables before
|
||||||
|
* using them.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* class MyEmbeddingFunction extends EmbeddingFunction {
|
||||||
|
* constructor(options: {model: string, timeout: number}) {
|
||||||
|
* super(optionsRaw);
|
||||||
|
* const options = this.resolveVariables(optionsRaw);
|
||||||
|
* this.model = options.model;
|
||||||
|
* this.timeout = options.timeout;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export abstract class EmbeddingFunction<
|
export abstract class EmbeddingFunction<
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: we don't know what the implementor will do
|
// biome-ignore lint/suspicious/noExplicitAny: we don't know what the implementor will do
|
||||||
@@ -44,33 +61,74 @@ export abstract class EmbeddingFunction<
|
|||||||
*/
|
*/
|
||||||
// biome-ignore lint/style/useNamingConvention: we want to keep the name as it is
|
// biome-ignore lint/style/useNamingConvention: we want to keep the name as it is
|
||||||
readonly TOptions!: M;
|
readonly TOptions!: M;
|
||||||
/**
|
|
||||||
* Convert the embedding function to a JSON object
|
|
||||||
* It is used to serialize the embedding function to the schema
|
|
||||||
* It's important that any object returned by this method contains all the necessary
|
|
||||||
* information to recreate the embedding function
|
|
||||||
*
|
|
||||||
* It should return the same object that was passed to the constructor
|
|
||||||
* If it does not, the embedding function will not be able to be recreated, or could be recreated incorrectly
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* class MyEmbeddingFunction extends EmbeddingFunction {
|
|
||||||
* constructor(options: {model: string, timeout: number}) {
|
|
||||||
* super();
|
|
||||||
* this.model = options.model;
|
|
||||||
* this.timeout = options.timeout;
|
|
||||||
* }
|
|
||||||
* toJSON() {
|
|
||||||
* return {
|
|
||||||
* model: this.model,
|
|
||||||
* timeout: this.timeout,
|
|
||||||
* };
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
abstract toJSON(): Partial<M>;
|
|
||||||
|
|
||||||
|
#config: Partial<M>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the original arguments to the constructor, to serialize them so they
|
||||||
|
* can be used to recreate the embedding function later.
|
||||||
|
*/
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny :
|
||||||
|
toJSON(): Record<string, any> {
|
||||||
|
return JSON.parse(JSON.stringify(this.#config));
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#config = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a list of keys in the function options that should be treated as
|
||||||
|
* sensitive. If users pass raw values for these keys, they will be rejected.
|
||||||
|
*/
|
||||||
|
protected getSensitiveKeys(): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply variables to the config.
|
||||||
|
*/
|
||||||
|
protected resolveVariables(config: Partial<M>): Partial<M> {
|
||||||
|
this.#config = config;
|
||||||
|
const registry = getRegistry();
|
||||||
|
const newConfig = { ...config };
|
||||||
|
for (const [key_, value] of Object.entries(newConfig)) {
|
||||||
|
if (
|
||||||
|
this.getSensitiveKeys().includes(key_) &&
|
||||||
|
!value.startsWith("$var:")
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`The key "${key_}" is sensitive and cannot be set directly. Please use the $var: syntax to set it.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Makes TS happy (https://stackoverflow.com/a/78391854)
|
||||||
|
const key = key_ as keyof M;
|
||||||
|
if (typeof value === "string" && value.startsWith("$var:")) {
|
||||||
|
const [name, defaultValue] = value.slice(5).split(":", 2);
|
||||||
|
const variableValue = registry.getVar(name);
|
||||||
|
if (!variableValue) {
|
||||||
|
if (defaultValue) {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny:
|
||||||
|
newConfig[key] = defaultValue as any;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Variable "${name}" not found`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny:
|
||||||
|
newConfig[key] = variableValue as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally load any resources needed for the embedding function.
|
||||||
|
*
|
||||||
|
* This method is called after the embedding function has been initialized
|
||||||
|
* but before any embeddings are computed. It is useful for loading local models
|
||||||
|
* or other resources that are needed for the embedding function to work.
|
||||||
|
*/
|
||||||
async init?(): Promise<void>;
|
async init?(): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ export class OpenAIEmbeddingFunction extends EmbeddingFunction<
|
|||||||
#modelName: OpenAIOptions["model"];
|
#modelName: OpenAIOptions["model"];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: Partial<OpenAIOptions> = {
|
optionsRaw: Partial<OpenAIOptions> = {
|
||||||
model: "text-embedding-ada-002",
|
model: "text-embedding-ada-002",
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
const options = this.resolveVariables(optionsRaw);
|
||||||
|
|
||||||
const openAIKey = options?.apiKey ?? process.env.OPENAI_API_KEY;
|
const openAIKey = options?.apiKey ?? process.env.OPENAI_API_KEY;
|
||||||
if (!openAIKey) {
|
if (!openAIKey) {
|
||||||
throw new Error("OpenAI API key is required");
|
throw new Error("OpenAI API key is required");
|
||||||
@@ -52,10 +54,8 @@ export class OpenAIEmbeddingFunction extends EmbeddingFunction<
|
|||||||
this.#modelName = modelName;
|
this.#modelName = modelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
protected getSensitiveKeys(): string[] {
|
||||||
return {
|
return ["apiKey"];
|
||||||
model: this.#modelName,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ndims(): number {
|
ndims(): number {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface EmbeddingFunctionCreate<T extends EmbeddingFunction> {
|
|||||||
*/
|
*/
|
||||||
export class EmbeddingFunctionRegistry {
|
export class EmbeddingFunctionRegistry {
|
||||||
#functions = new Map<string, EmbeddingFunctionConstructor>();
|
#functions = new Map<string, EmbeddingFunctionConstructor>();
|
||||||
|
#variables = new Map<string, string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of registered functions
|
* Get the number of registered functions
|
||||||
@@ -82,10 +83,7 @@ export class EmbeddingFunctionRegistry {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
create = function (options?: any) {
|
create = (options?: any) => new factory(options);
|
||||||
const instance = new factory(options);
|
|
||||||
return instance;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -164,6 +162,37 @@ export class EmbeddingFunctionRegistry {
|
|||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a variable. These can be accessed in the embedding function
|
||||||
|
* configuration using the syntax `$var:variable_name`. If they are not
|
||||||
|
* set, an error will be thrown letting you know which key is unset. If you
|
||||||
|
* want to supply a default value, you can add an additional part in the
|
||||||
|
* configuration like so: `$var:variable_name:default_value`. Default values
|
||||||
|
* can be used for runtime configurations that are not sensitive, such as
|
||||||
|
* whether to use a GPU for inference.
|
||||||
|
*
|
||||||
|
* The name must not contain colons. The default value can contain colons.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
setVar(name: string, value: string): void {
|
||||||
|
if (name.includes(":")) {
|
||||||
|
throw new Error("Variable names cannot contain colons");
|
||||||
|
}
|
||||||
|
this.#variables.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a variable.
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
* @see {@link setVar}
|
||||||
|
*/
|
||||||
|
getVar(name: string): string | undefined {
|
||||||
|
return this.#variables.get(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _REGISTRY = new EmbeddingFunctionRegistry();
|
const _REGISTRY = new EmbeddingFunctionRegistry();
|
||||||
|
|||||||
@@ -44,11 +44,12 @@ export class TransformersEmbeddingFunction extends EmbeddingFunction<
|
|||||||
#ndims?: number;
|
#ndims?: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: Partial<XenovaTransformerOptions> = {
|
optionsRaw: Partial<XenovaTransformerOptions> = {
|
||||||
model: "Xenova/all-MiniLM-L6-v2",
|
model: "Xenova/all-MiniLM-L6-v2",
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
const options = this.resolveVariables(optionsRaw);
|
||||||
|
|
||||||
const modelName = options?.model ?? "Xenova/all-MiniLM-L6-v2";
|
const modelName = options?.model ?? "Xenova/all-MiniLM-L6-v2";
|
||||||
this.#tokenizerOptions = {
|
this.#tokenizerOptions = {
|
||||||
@@ -59,22 +60,6 @@ export class TransformersEmbeddingFunction extends EmbeddingFunction<
|
|||||||
this.#ndims = options.ndims;
|
this.#ndims = options.ndims;
|
||||||
this.#modelName = modelName;
|
this.#modelName = modelName;
|
||||||
}
|
}
|
||||||
toJSON() {
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
const obj: Record<string, any> = {
|
|
||||||
model: this.#modelName,
|
|
||||||
};
|
|
||||||
if (this.#ndims) {
|
|
||||||
obj["ndims"] = this.#ndims;
|
|
||||||
}
|
|
||||||
if (this.#tokenizerOptions) {
|
|
||||||
obj["tokenizerOptions"] = this.#tokenizerOptions;
|
|
||||||
}
|
|
||||||
if (this.#tokenizer) {
|
|
||||||
obj["tokenizer"] = this.#tokenizer.name;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
let transformers;
|
let transformers;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.darwin-arm64.node",
|
"main": "lancedb.darwin-arm64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-x64",
|
"name": "@lancedb/lancedb-darwin-x64",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.darwin-x64.node",
|
"main": "lancedb.darwin-x64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-gnu.node",
|
"main": "lancedb.linux-arm64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-musl",
|
"name": "@lancedb/lancedb-linux-arm64-musl",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-musl.node",
|
"main": "lancedb.linux-arm64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-gnu",
|
"name": "@lancedb/lancedb-linux-x64-gnu",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-gnu.node",
|
"main": "lancedb.linux-x64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-musl",
|
"name": "@lancedb/lancedb-linux-x64-musl",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-musl.node",
|
"main": "lancedb.linux-x64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
4
nodejs/package-lock.json
generated
4
nodejs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.15.1-beta.2",
|
"version": "0.18.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
|||||||
@@ -2,17 +2,15 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
|
use lancedb::database::CreateTableMode;
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use napi_derive::*;
|
use napi_derive::*;
|
||||||
|
|
||||||
use crate::error::{convert_error, NapiErrorExt};
|
use crate::error::NapiErrorExt;
|
||||||
use crate::table::Table;
|
use crate::table::Table;
|
||||||
use crate::ConnectionOptions;
|
use crate::ConnectionOptions;
|
||||||
use lancedb::connection::{
|
use lancedb::connection::{ConnectBuilder, Connection as LanceDBConnection};
|
||||||
ConnectBuilder, Connection as LanceDBConnection, CreateTableMode, LanceFileVersion,
|
|
||||||
};
|
|
||||||
use lancedb::ipc::{ipc_file_to_batches, ipc_file_to_schema};
|
use lancedb::ipc::{ipc_file_to_batches, ipc_file_to_schema};
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
@@ -124,8 +122,6 @@ impl Connection {
|
|||||||
buf: Buffer,
|
buf: Buffer,
|
||||||
mode: String,
|
mode: String,
|
||||||
storage_options: Option<HashMap<String, String>>,
|
storage_options: Option<HashMap<String, String>>,
|
||||||
data_storage_options: Option<String>,
|
|
||||||
enable_v2_manifest_paths: Option<bool>,
|
|
||||||
) -> napi::Result<Table> {
|
) -> napi::Result<Table> {
|
||||||
let batches = ipc_file_to_batches(buf.to_vec())
|
let batches = ipc_file_to_batches(buf.to_vec())
|
||||||
.map_err(|e| napi::Error::from_reason(format!("Failed to read IPC file: {}", e)))?;
|
.map_err(|e| napi::Error::from_reason(format!("Failed to read IPC file: {}", e)))?;
|
||||||
@@ -137,14 +133,6 @@ impl Connection {
|
|||||||
builder = builder.storage_option(key, value);
|
builder = builder.storage_option(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(data_storage_option) = data_storage_options.as_ref() {
|
|
||||||
builder = builder.data_storage_version(
|
|
||||||
LanceFileVersion::from_str(data_storage_option).map_err(|e| convert_error(&e))?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(enable_v2_manifest_paths) = enable_v2_manifest_paths {
|
|
||||||
builder = builder.enable_v2_manifest_paths(enable_v2_manifest_paths);
|
|
||||||
}
|
|
||||||
let tbl = builder.execute().await.default_error()?;
|
let tbl = builder.execute().await.default_error()?;
|
||||||
Ok(Table::new(tbl))
|
Ok(Table::new(tbl))
|
||||||
}
|
}
|
||||||
@@ -156,8 +144,6 @@ impl Connection {
|
|||||||
schema_buf: Buffer,
|
schema_buf: Buffer,
|
||||||
mode: String,
|
mode: String,
|
||||||
storage_options: Option<HashMap<String, String>>,
|
storage_options: Option<HashMap<String, String>>,
|
||||||
data_storage_options: Option<String>,
|
|
||||||
enable_v2_manifest_paths: Option<bool>,
|
|
||||||
) -> napi::Result<Table> {
|
) -> napi::Result<Table> {
|
||||||
let schema = ipc_file_to_schema(schema_buf.to_vec()).map_err(|e| {
|
let schema = ipc_file_to_schema(schema_buf.to_vec()).map_err(|e| {
|
||||||
napi::Error::from_reason(format!("Failed to marshal schema from JS to Rust: {}", e))
|
napi::Error::from_reason(format!("Failed to marshal schema from JS to Rust: {}", e))
|
||||||
@@ -172,14 +158,6 @@ impl Connection {
|
|||||||
builder = builder.storage_option(key, value);
|
builder = builder.storage_option(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(data_storage_option) = data_storage_options.as_ref() {
|
|
||||||
builder = builder.data_storage_version(
|
|
||||||
LanceFileVersion::from_str(data_storage_option).map_err(|e| convert_error(&e))?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(enable_v2_manifest_paths) = enable_v2_manifest_paths {
|
|
||||||
builder = builder.enable_v2_manifest_paths(enable_v2_manifest_paths);
|
|
||||||
}
|
|
||||||
let tbl = builder.execute().await.default_error()?;
|
let tbl = builder.execute().await.default_error()?;
|
||||||
Ok(Table::new(tbl))
|
Ok(Table::new(tbl))
|
||||||
}
|
}
|
||||||
@@ -209,4 +187,9 @@ impl Connection {
|
|||||||
pub async fn drop_table(&self, name: String) -> napi::Result<()> {
|
pub async fn drop_table(&self, name: String) -> napi::Result<()> {
|
||||||
self.get_inner()?.drop_table(&name).await.default_error()
|
self.get_inner()?.drop_table(&name).await.default_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi(catch_unwind)]
|
||||||
|
pub async fn drop_all_tables(&self) -> napi::Result<()> {
|
||||||
|
self.get_inner()?.drop_all_tables().await.default_error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use napi_derive::*;
|
use napi_derive::*;
|
||||||
|
|
||||||
/// Timeout configuration for remote HTTP client.
|
/// Timeout configuration for remote HTTP client.
|
||||||
@@ -67,6 +69,7 @@ pub struct ClientConfig {
|
|||||||
pub user_agent: Option<String>,
|
pub user_agent: Option<String>,
|
||||||
pub retry_config: Option<RetryConfig>,
|
pub retry_config: Option<RetryConfig>,
|
||||||
pub timeout_config: Option<TimeoutConfig>,
|
pub timeout_config: Option<TimeoutConfig>,
|
||||||
|
pub extra_headers: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TimeoutConfig> for lancedb::remote::TimeoutConfig {
|
impl From<TimeoutConfig> for lancedb::remote::TimeoutConfig {
|
||||||
@@ -104,6 +107,7 @@ impl From<ClientConfig> for lancedb::remote::ClientConfig {
|
|||||||
.unwrap_or(concat!("LanceDB-Node-Client/", env!("CARGO_PKG_VERSION")).to_string()),
|
.unwrap_or(concat!("LanceDB-Node-Client/", env!("CARGO_PKG_VERSION")).to_string()),
|
||||||
retry_config: config.retry_config.map(Into::into).unwrap_or_default(),
|
retry_config: config.retry_config.map(Into::into).unwrap_or_default(),
|
||||||
timeout_config: config.timeout_config.map(Into::into).unwrap_or_default(),
|
timeout_config: config.timeout_config.map(Into::into).unwrap_or_default(),
|
||||||
|
extra_headers: config.extra_headers.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
pyright_report.csv
Normal file
56
pyright_report.csv
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
file,errors,warnings,total_issues
|
||||||
|
python/python/lancedb/arrow.py,0,0,0
|
||||||
|
python/python/lancedb/background_loop.py,0,0,0
|
||||||
|
python/python/lancedb/embeddings/__init__.py,0,0,0
|
||||||
|
python/python/lancedb/exceptions.py,0,0,0
|
||||||
|
python/python/lancedb/index.py,0,0,0
|
||||||
|
python/python/lancedb/integrations/__init__.py,0,0,0
|
||||||
|
python/python/lancedb/remote/__init__.py,0,0,0
|
||||||
|
python/python/lancedb/remote/errors.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/__init__.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/answerdotai.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/cohere.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/colbert.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/cross_encoder.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/openai.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/util.py,0,0,0
|
||||||
|
python/python/lancedb/rerankers/voyageai.py,0,0,0
|
||||||
|
python/python/lancedb/schema.py,0,0,0
|
||||||
|
python/python/lancedb/types.py,0,0,0
|
||||||
|
python/python/lancedb/__init__.py,0,1,1
|
||||||
|
python/python/lancedb/conftest.py,1,0,1
|
||||||
|
python/python/lancedb/embeddings/bedrock.py,1,0,1
|
||||||
|
python/python/lancedb/merge.py,1,0,1
|
||||||
|
python/python/lancedb/rerankers/base.py,1,0,1
|
||||||
|
python/python/lancedb/rerankers/jinaai.py,0,1,1
|
||||||
|
python/python/lancedb/rerankers/linear_combination.py,1,0,1
|
||||||
|
python/python/lancedb/embeddings/instructor.py,2,0,2
|
||||||
|
python/python/lancedb/embeddings/openai.py,2,0,2
|
||||||
|
python/python/lancedb/embeddings/watsonx.py,2,0,2
|
||||||
|
python/python/lancedb/embeddings/registry.py,3,0,3
|
||||||
|
python/python/lancedb/embeddings/sentence_transformers.py,3,0,3
|
||||||
|
python/python/lancedb/integrations/pyarrow.py,3,0,3
|
||||||
|
python/python/lancedb/rerankers/rrf.py,3,0,3
|
||||||
|
python/python/lancedb/dependencies.py,4,0,4
|
||||||
|
python/python/lancedb/embeddings/gemini_text.py,4,0,4
|
||||||
|
python/python/lancedb/embeddings/gte.py,4,0,4
|
||||||
|
python/python/lancedb/embeddings/gte_mlx_model.py,4,0,4
|
||||||
|
python/python/lancedb/embeddings/ollama.py,4,0,4
|
||||||
|
python/python/lancedb/embeddings/transformers.py,4,0,4
|
||||||
|
python/python/lancedb/remote/db.py,5,0,5
|
||||||
|
python/python/lancedb/context.py,6,0,6
|
||||||
|
python/python/lancedb/embeddings/cohere.py,6,0,6
|
||||||
|
python/python/lancedb/fts.py,6,0,6
|
||||||
|
python/python/lancedb/db.py,9,0,9
|
||||||
|
python/python/lancedb/embeddings/utils.py,9,0,9
|
||||||
|
python/python/lancedb/common.py,11,0,11
|
||||||
|
python/python/lancedb/util.py,13,0,13
|
||||||
|
python/python/lancedb/embeddings/imagebind.py,14,0,14
|
||||||
|
python/python/lancedb/embeddings/voyageai.py,15,0,15
|
||||||
|
python/python/lancedb/embeddings/open_clip.py,16,0,16
|
||||||
|
python/python/lancedb/pydantic.py,16,0,16
|
||||||
|
python/python/lancedb/embeddings/base.py,17,0,17
|
||||||
|
python/python/lancedb/embeddings/jinaai.py,18,1,19
|
||||||
|
python/python/lancedb/remote/table.py,23,0,23
|
||||||
|
python/python/lancedb/query.py,47,1,48
|
||||||
|
python/python/lancedb/table.py,61,0,61
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.18.1-beta.3"
|
current_version = "0.21.1"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
2
python/.gitignore
vendored
Normal file
2
python/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Test data created by some example tests
|
||||||
|
data/
|
||||||
@@ -8,9 +8,9 @@ For general contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|||||||
The Python package is a wrapper around the Rust library, `lancedb`. We use
|
The Python package is a wrapper around the Rust library, `lancedb`. We use
|
||||||
[pyo3](https://pyo3.rs/) to create the bindings between Rust and Python.
|
[pyo3](https://pyo3.rs/) to create the bindings between Rust and Python.
|
||||||
|
|
||||||
* `src/`: Rust bindings source code
|
- `src/`: Rust bindings source code
|
||||||
* `python/lancedb`: Python package source code
|
- `python/lancedb`: Python package source code
|
||||||
* `python/tests`: Unit tests
|
- `python/tests`: Unit tests
|
||||||
|
|
||||||
## Development environment
|
## Development environment
|
||||||
|
|
||||||
@@ -61,6 +61,12 @@ make test
|
|||||||
make doctest
|
make doctest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run type checking:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make typecheck
|
||||||
|
```
|
||||||
|
|
||||||
To run a single test, you can use the `pytest` command directly. Provide the path
|
To run a single test, you can use the `pytest` command directly. Provide the path
|
||||||
to the test file, and optionally the test name after `::`.
|
to the test file, and optionally the test name after `::`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.18.1-beta.3"
|
version = "0.21.1"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -14,21 +14,20 @@ name = "_lancedb"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrow = { version = "53.2", features = ["pyarrow"] }
|
arrow = { version = "54.1", features = ["pyarrow"] }
|
||||||
lancedb = { path = "../rust/lancedb", default-features = false }
|
lancedb = { path = "../rust/lancedb", default-features = false }
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
pyo3 = { version = "0.22.2", features = [
|
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py39"] }
|
||||||
"extension-module",
|
pyo3-async-runtimes = { version = "0.23", features = [
|
||||||
"abi3-py39",
|
"attributes",
|
||||||
"gil-refs"
|
"tokio-runtime",
|
||||||
] }
|
] }
|
||||||
pyo3-async-runtimes = { version = "0.22", features = ["attributes", "tokio-runtime"] }
|
|
||||||
pin-project = "1.1.5"
|
pin-project = "1.1.5"
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
tokio = { version = "1.40", features = ["sync"] }
|
tokio = { version = "1.40", features = ["sync"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pyo3-build-config = { version = "0.20.3", features = [
|
pyo3-build-config = { version = "0.23", features = [
|
||||||
"extension-module",
|
"extension-module",
|
||||||
"abi3-py39",
|
"abi3-py39",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -23,10 +23,18 @@ check: ## Check formatting and lints.
|
|||||||
fix: ## Fix python lints
|
fix: ## Fix python lints
|
||||||
ruff check python --fix
|
ruff check python --fix
|
||||||
|
|
||||||
|
.PHONY: typecheck
|
||||||
|
typecheck: ## Run type checking with pyright.
|
||||||
|
pyright
|
||||||
|
|
||||||
.PHONY: doctest
|
.PHONY: doctest
|
||||||
doctest: ## Run documentation tests.
|
doctest: ## Run documentation tests.
|
||||||
pytest --doctest-modules python/lancedb
|
pytest --doctest-modules python/lancedb
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ## Run tests.
|
test: ## Run tests.
|
||||||
pytest python/tests -vv --durations=10 -m "not slow"
|
pytest python/tests -vv --durations=10 -m "not slow and not s3_test"
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf data
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ name = "lancedb"
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deprecation",
|
"deprecation",
|
||||||
"pylance==0.23.0b4",
|
|
||||||
"tqdm>=4.27.0",
|
"tqdm>=4.27.0",
|
||||||
|
"pyarrow>=14",
|
||||||
"pydantic>=1.10",
|
"pydantic>=1.10",
|
||||||
"packaging",
|
"packaging",
|
||||||
"overrides>=0.7",
|
"overrides>=0.7",
|
||||||
|
"pylance>=0.23.2",
|
||||||
]
|
]
|
||||||
description = "lancedb"
|
description = "lancedb"
|
||||||
authors = [{ name = "LanceDB Devs", email = "dev@lancedb.com" }]
|
authors = [{ name = "LanceDB Devs", email = "dev@lancedb.com" }]
|
||||||
@@ -55,7 +56,12 @@ tests = [
|
|||||||
"tantivy",
|
"tantivy",
|
||||||
"pyarrow-stubs",
|
"pyarrow-stubs",
|
||||||
]
|
]
|
||||||
dev = ["ruff", "pre-commit", "pyright", 'typing-extensions>=4.0.0; python_version < "3.11"']
|
dev = [
|
||||||
|
"ruff",
|
||||||
|
"pre-commit",
|
||||||
|
"pyright",
|
||||||
|
'typing-extensions>=4.0.0; python_version < "3.11"',
|
||||||
|
]
|
||||||
docs = ["mkdocs", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]"]
|
docs = ["mkdocs", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]"]
|
||||||
clip = ["torch", "pillow", "open-clip"]
|
clip = ["torch", "pillow", "open-clip"]
|
||||||
embeddings = [
|
embeddings = [
|
||||||
@@ -86,7 +92,7 @@ requires = ["maturin>=1.4"]
|
|||||||
build-backend = "maturin"
|
build-backend = "maturin"
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["F", "E", "W", "G", "TCH", "PERF"]
|
select = ["F", "E", "W", "G", "PERF"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--strict-markers --ignore-glob=lancedb/embeddings/*.py"
|
addopts = "--strict-markers --ignore-glob=lancedb/embeddings/*.py"
|
||||||
@@ -97,5 +103,28 @@ markers = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
include = ["python/lancedb/table.py"]
|
include = [
|
||||||
|
"python/lancedb/index.py",
|
||||||
|
"python/lancedb/rerankers/util.py",
|
||||||
|
"python/lancedb/rerankers/__init__.py",
|
||||||
|
"python/lancedb/rerankers/voyageai.py",
|
||||||
|
"python/lancedb/rerankers/jinaai.py",
|
||||||
|
"python/lancedb/rerankers/openai.py",
|
||||||
|
"python/lancedb/rerankers/cross_encoder.py",
|
||||||
|
"python/lancedb/rerankers/colbert.py",
|
||||||
|
"python/lancedb/rerankers/answerdotai.py",
|
||||||
|
"python/lancedb/rerankers/cohere.py",
|
||||||
|
"python/lancedb/arrow.py",
|
||||||
|
"python/lancedb/__init__.py",
|
||||||
|
"python/lancedb/types.py",
|
||||||
|
"python/lancedb/integrations/__init__.py",
|
||||||
|
"python/lancedb/exceptions.py",
|
||||||
|
"python/lancedb/background_loop.py",
|
||||||
|
"python/lancedb/schema.py",
|
||||||
|
"python/lancedb/remote/__init__.py",
|
||||||
|
"python/lancedb/remote/errors.py",
|
||||||
|
"python/lancedb/embeddings/__init__.py",
|
||||||
|
"python/lancedb/_lancedb.pyi",
|
||||||
|
]
|
||||||
|
exclude = ["python/tests/"]
|
||||||
pythonVersion = "3.12"
|
pythonVersion = "3.12"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from ._lancedb import connect as lancedb_connect
|
|||||||
from .common import URI, sanitize_uri
|
from .common import URI, sanitize_uri
|
||||||
from .db import AsyncConnection, DBConnection, LanceDBConnection
|
from .db import AsyncConnection, DBConnection, LanceDBConnection
|
||||||
from .remote import ClientConfig
|
from .remote import ClientConfig
|
||||||
|
from .remote.db import RemoteDBConnection
|
||||||
from .schema import vector
|
from .schema import vector
|
||||||
from .table import AsyncTable
|
from .table import AsyncTable
|
||||||
|
|
||||||
@@ -86,8 +87,6 @@ def connect(
|
|||||||
conn : DBConnection
|
conn : DBConnection
|
||||||
A connection to a LanceDB database.
|
A connection to a LanceDB database.
|
||||||
"""
|
"""
|
||||||
from .remote.db import RemoteDBConnection
|
|
||||||
|
|
||||||
if isinstance(uri, str) and uri.startswith("db://"):
|
if isinstance(uri, str) and uri.startswith("db://"):
|
||||||
if api_key is None:
|
if api_key is None:
|
||||||
api_key = os.environ.get("LANCEDB_API_KEY")
|
api_key = os.environ.get("LANCEDB_API_KEY")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Dict, List, Optional, Tuple, Any, Union, Literal
|
|||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
|
|
||||||
from .index import BTree, IvfFlat, IvfPq, Bitmap, LabelList, HnswPq, HnswSq, FTS
|
from .index import BTree, IvfFlat, IvfPq, Bitmap, LabelList, HnswPq, HnswSq, FTS
|
||||||
|
from .remote import ClientConfig
|
||||||
|
|
||||||
class Connection(object):
|
class Connection(object):
|
||||||
uri: str
|
uri: str
|
||||||
@@ -15,8 +16,6 @@ class Connection(object):
|
|||||||
mode: str,
|
mode: str,
|
||||||
data: pa.RecordBatchReader,
|
data: pa.RecordBatchReader,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
data_storage_version: Optional[str] = None,
|
|
||||||
enable_v2_manifest_paths: Optional[bool] = None,
|
|
||||||
) -> Table: ...
|
) -> Table: ...
|
||||||
async def create_empty_table(
|
async def create_empty_table(
|
||||||
self,
|
self,
|
||||||
@@ -24,8 +23,6 @@ class Connection(object):
|
|||||||
mode: str,
|
mode: str,
|
||||||
schema: pa.Schema,
|
schema: pa.Schema,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
data_storage_version: Optional[str] = None,
|
|
||||||
enable_v2_manifest_paths: Optional[bool] = None,
|
|
||||||
) -> Table: ...
|
) -> Table: ...
|
||||||
async def rename_table(self, old_name: str, new_name: str) -> None: ...
|
async def rename_table(self, old_name: str, new_name: str) -> None: ...
|
||||||
async def drop_table(self, name: str) -> None: ...
|
async def drop_table(self, name: str) -> None: ...
|
||||||
@@ -75,11 +72,15 @@ async def connect(
|
|||||||
region: Optional[str],
|
region: Optional[str],
|
||||||
host_override: Optional[str],
|
host_override: Optional[str],
|
||||||
read_consistency_interval: Optional[float],
|
read_consistency_interval: Optional[float],
|
||||||
|
client_config: Optional[Union[ClientConfig, Dict[str, Any]]],
|
||||||
|
storage_options: Optional[Dict[str, str]],
|
||||||
) -> Connection: ...
|
) -> Connection: ...
|
||||||
|
|
||||||
class RecordBatchStream:
|
class RecordBatchStream:
|
||||||
|
@property
|
||||||
def schema(self) -> pa.Schema: ...
|
def schema(self) -> pa.Schema: ...
|
||||||
async def next(self) -> Optional[pa.RecordBatch]: ...
|
def __aiter__(self) -> "RecordBatchStream": ...
|
||||||
|
async def __anext__(self) -> pa.RecordBatch: ...
|
||||||
|
|
||||||
class Query:
|
class Query:
|
||||||
def where(self, filter: str): ...
|
def where(self, filter: str): ...
|
||||||
@@ -146,6 +147,10 @@ class CompactionStats:
|
|||||||
files_removed: int
|
files_removed: int
|
||||||
files_added: int
|
files_added: int
|
||||||
|
|
||||||
|
class CleanupStats:
|
||||||
|
bytes_removed: int
|
||||||
|
old_versions: int
|
||||||
|
|
||||||
class RemovalStats:
|
class RemovalStats:
|
||||||
bytes_removed: int
|
bytes_removed: int
|
||||||
old_versions_removed: int
|
old_versions_removed: int
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
|
|
||||||
@@ -66,3 +66,17 @@ class AsyncRecordBatchReader:
|
|||||||
batches = table.to_batches(max_chunksize=max_batch_length)
|
batches = table.to_batches(max_chunksize=max_batch_length)
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
yield batch
|
yield batch
|
||||||
|
|
||||||
|
|
||||||
|
def peek_reader(
|
||||||
|
reader: pa.RecordBatchReader,
|
||||||
|
) -> Tuple[pa.RecordBatch, pa.RecordBatchReader]:
|
||||||
|
if not isinstance(reader, pa.RecordBatchReader):
|
||||||
|
raise TypeError("reader must be a RecordBatchReader")
|
||||||
|
batch = reader.read_next_batch()
|
||||||
|
|
||||||
|
def all_batches():
|
||||||
|
yield batch
|
||||||
|
yield from reader
|
||||||
|
|
||||||
|
return batch, pa.RecordBatchReader.from_batches(batch.schema, all_batches())
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from overrides import EnforceOverrides, override # type: ignore
|
|||||||
from lancedb.common import data_to_reader, sanitize_uri, validate_schema
|
from lancedb.common import data_to_reader, sanitize_uri, validate_schema
|
||||||
from lancedb.background_loop import LOOP
|
from lancedb.background_loop import LOOP
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
from ._lancedb import connect as lancedb_connect # type: ignore
|
from ._lancedb import connect as lancedb_connect # type: ignore
|
||||||
from .table import (
|
from .table import (
|
||||||
AsyncTable,
|
AsyncTable,
|
||||||
@@ -26,6 +27,8 @@ from .util import (
|
|||||||
validate_table_name,
|
validate_table_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import deprecation
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
from .pydantic import LanceModel
|
from .pydantic import LanceModel
|
||||||
@@ -119,19 +122,11 @@ class DBConnection(EnforceOverrides):
|
|||||||
See available options at
|
See available options at
|
||||||
<https://lancedb.github.io/lancedb/guides/storage/>
|
<https://lancedb.github.io/lancedb/guides/storage/>
|
||||||
data_storage_version: optional, str, default "stable"
|
data_storage_version: optional, str, default "stable"
|
||||||
The version of the data storage format to use. Newer versions are more
|
Deprecated. Set `storage_options` when connecting to the database and set
|
||||||
efficient but require newer versions of lance to read. The default is
|
`new_table_data_storage_version` in the options.
|
||||||
"stable" which will use the legacy v2 version. See the user guide
|
enable_v2_manifest_paths: optional, bool, default False
|
||||||
for more details.
|
Deprecated. Set `storage_options` when connecting to the database and set
|
||||||
enable_v2_manifest_paths: bool, optional, default False
|
`new_table_enable_v2_manifest_paths` in the options.
|
||||||
Use the new V2 manifest paths. These paths provide more efficient
|
|
||||||
opening of datasets with many versions on object stores. WARNING:
|
|
||||||
turning this on will make the dataset unreadable for older versions
|
|
||||||
of LanceDB (prior to 0.13.0). To migrate an existing dataset, instead
|
|
||||||
use the
|
|
||||||
[Table.migrate_manifest_paths_v2][lancedb.table.Table.migrate_v2_manifest_paths]
|
|
||||||
method.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
LanceTable
|
LanceTable
|
||||||
@@ -302,6 +297,12 @@ class DBConnection(EnforceOverrides):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def drop_all_tables(self):
|
||||||
|
"""
|
||||||
|
Drop all tables from the database
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def uri(self) -> str:
|
def uri(self) -> str:
|
||||||
return self._uri
|
return self._uri
|
||||||
@@ -452,8 +453,6 @@ class LanceDBConnection(DBConnection):
|
|||||||
fill_value=fill_value,
|
fill_value=fill_value,
|
||||||
embedding_functions=embedding_functions,
|
embedding_functions=embedding_functions,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
data_storage_version=data_storage_version,
|
|
||||||
enable_v2_manifest_paths=enable_v2_manifest_paths,
|
|
||||||
)
|
)
|
||||||
return tbl
|
return tbl
|
||||||
|
|
||||||
@@ -496,9 +495,19 @@ class LanceDBConnection(DBConnection):
|
|||||||
"""
|
"""
|
||||||
LOOP.run(self._conn.drop_table(name, ignore_missing=ignore_missing))
|
LOOP.run(self._conn.drop_table(name, ignore_missing=ignore_missing))
|
||||||
|
|
||||||
|
@override
|
||||||
|
def drop_all_tables(self):
|
||||||
|
LOOP.run(self._conn.drop_all_tables())
|
||||||
|
|
||||||
|
@deprecation.deprecated(
|
||||||
|
deprecated_in="0.15.1",
|
||||||
|
removed_in="0.17",
|
||||||
|
current_version=__version__,
|
||||||
|
details="Use drop_all_tables() instead",
|
||||||
|
)
|
||||||
@override
|
@override
|
||||||
def drop_database(self):
|
def drop_database(self):
|
||||||
LOOP.run(self._conn.drop_database())
|
LOOP.run(self._conn.drop_all_tables())
|
||||||
|
|
||||||
|
|
||||||
class AsyncConnection(object):
|
class AsyncConnection(object):
|
||||||
@@ -595,9 +604,6 @@ class AsyncConnection(object):
|
|||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
*,
|
*,
|
||||||
embedding_functions: Optional[List[EmbeddingFunctionConfig]] = None,
|
embedding_functions: Optional[List[EmbeddingFunctionConfig]] = None,
|
||||||
data_storage_version: Optional[str] = None,
|
|
||||||
use_legacy_format: Optional[bool] = None,
|
|
||||||
enable_v2_manifest_paths: Optional[bool] = None,
|
|
||||||
) -> AsyncTable:
|
) -> AsyncTable:
|
||||||
"""Create an [AsyncTable][lancedb.table.AsyncTable] in the database.
|
"""Create an [AsyncTable][lancedb.table.AsyncTable] in the database.
|
||||||
|
|
||||||
@@ -640,23 +646,6 @@ class AsyncConnection(object):
|
|||||||
connection will be inherited by the table, but can be overridden here.
|
connection will be inherited by the table, but can be overridden here.
|
||||||
See available options at
|
See available options at
|
||||||
<https://lancedb.github.io/lancedb/guides/storage/>
|
<https://lancedb.github.io/lancedb/guides/storage/>
|
||||||
data_storage_version: optional, str, default "stable"
|
|
||||||
The version of the data storage format to use. Newer versions are more
|
|
||||||
efficient but require newer versions of lance to read. The default is
|
|
||||||
"stable" which will use the legacy v2 version. See the user guide
|
|
||||||
for more details.
|
|
||||||
use_legacy_format: bool, optional, default False. (Deprecated)
|
|
||||||
If True, use the legacy format for the table. If False, use the new format.
|
|
||||||
This method is deprecated, use `data_storage_version` instead.
|
|
||||||
enable_v2_manifest_paths: bool, optional, default False
|
|
||||||
Use the new V2 manifest paths. These paths provide more efficient
|
|
||||||
opening of datasets with many versions on object stores. WARNING:
|
|
||||||
turning this on will make the dataset unreadable for older versions
|
|
||||||
of LanceDB (prior to 0.13.0). To migrate an existing dataset, instead
|
|
||||||
use the
|
|
||||||
[AsyncTable.migrate_manifest_paths_v2][lancedb.table.AsyncTable.migrate_manifest_paths_v2]
|
|
||||||
method.
|
|
||||||
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -795,17 +784,12 @@ class AsyncConnection(object):
|
|||||||
if mode == "create" and exist_ok:
|
if mode == "create" and exist_ok:
|
||||||
mode = "exist_ok"
|
mode = "exist_ok"
|
||||||
|
|
||||||
if not data_storage_version:
|
|
||||||
data_storage_version = "legacy" if use_legacy_format else "stable"
|
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
new_table = await self._inner.create_empty_table(
|
new_table = await self._inner.create_empty_table(
|
||||||
name,
|
name,
|
||||||
mode,
|
mode,
|
||||||
schema,
|
schema,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
data_storage_version=data_storage_version,
|
|
||||||
enable_v2_manifest_paths=enable_v2_manifest_paths,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = data_to_reader(data, schema)
|
data = data_to_reader(data, schema)
|
||||||
@@ -814,8 +798,6 @@ class AsyncConnection(object):
|
|||||||
mode,
|
mode,
|
||||||
data,
|
data,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
data_storage_version=data_storage_version,
|
|
||||||
enable_v2_manifest_paths=enable_v2_manifest_paths,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return AsyncTable(new_table)
|
return AsyncTable(new_table)
|
||||||
@@ -885,9 +867,19 @@ class AsyncConnection(object):
|
|||||||
if f"Table '{name}' was not found" not in str(e):
|
if f"Table '{name}' was not found" not in str(e):
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
async def drop_all_tables(self):
|
||||||
|
"""Drop all tables from the database."""
|
||||||
|
await self._inner.drop_all_tables()
|
||||||
|
|
||||||
|
@deprecation.deprecated(
|
||||||
|
deprecated_in="0.15.1",
|
||||||
|
removed_in="0.17",
|
||||||
|
current_version=__version__,
|
||||||
|
details="Use drop_all_tables() instead",
|
||||||
|
)
|
||||||
async def drop_database(self):
|
async def drop_database(self):
|
||||||
"""
|
"""
|
||||||
Drop database
|
Drop database
|
||||||
This is the same thing as dropping all the tables
|
This is the same thing as dropping all the tables
|
||||||
"""
|
"""
|
||||||
await self._inner.drop_db()
|
await self._inner.drop_all_tables()
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
import copy
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
|
from lancedb.util import add_note
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
from pydantic import BaseModel, Field, PrivateAttr
|
from pydantic import BaseModel, Field, PrivateAttr
|
||||||
@@ -28,13 +30,67 @@ class EmbeddingFunction(BaseModel, ABC):
|
|||||||
7 # Setting 0 disables retires. Maybe this should not be enabled by default,
|
7 # Setting 0 disables retires. Maybe this should not be enabled by default,
|
||||||
)
|
)
|
||||||
_ndims: int = PrivateAttr()
|
_ndims: int = PrivateAttr()
|
||||||
|
_original_args: dict = PrivateAttr()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, **kwargs):
|
def create(cls, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create an instance of the embedding function
|
Create an instance of the embedding function
|
||||||
"""
|
"""
|
||||||
return cls(**kwargs)
|
resolved_kwargs = cls.__resolveVariables(kwargs)
|
||||||
|
instance = cls(**resolved_kwargs)
|
||||||
|
instance._original_args = kwargs
|
||||||
|
return instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __resolveVariables(cls, args: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Resolve variables in the args
|
||||||
|
"""
|
||||||
|
from .registry import EmbeddingFunctionRegistry
|
||||||
|
|
||||||
|
new_args = copy.deepcopy(args)
|
||||||
|
|
||||||
|
registry = EmbeddingFunctionRegistry.get_instance()
|
||||||
|
sensitive_keys = cls.sensitive_keys()
|
||||||
|
for k, v in new_args.items():
|
||||||
|
if isinstance(v, str) and not v.startswith("$var:") and k in sensitive_keys:
|
||||||
|
exc = ValueError(
|
||||||
|
f"Sensitive key '{k}' cannot be set to a hardcoded value"
|
||||||
|
)
|
||||||
|
add_note(exc, "Help: Use $var: to set sensitive keys to variables")
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
if isinstance(v, str) and v.startswith("$var:"):
|
||||||
|
parts = v[5:].split(":", maxsplit=1)
|
||||||
|
if len(parts) == 1:
|
||||||
|
try:
|
||||||
|
new_args[k] = registry.get_var(parts[0])
|
||||||
|
except KeyError:
|
||||||
|
exc = ValueError(
|
||||||
|
"Variable '{}' not found in registry".format(parts[0])
|
||||||
|
)
|
||||||
|
add_note(
|
||||||
|
exc,
|
||||||
|
"Help: Variables are reset in new Python sessions. "
|
||||||
|
"Use `registry.set_var` to set variables.",
|
||||||
|
)
|
||||||
|
raise exc
|
||||||
|
else:
|
||||||
|
name, default = parts
|
||||||
|
try:
|
||||||
|
new_args[k] = registry.get_var(name)
|
||||||
|
except KeyError:
|
||||||
|
new_args[k] = default
|
||||||
|
return new_args
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sensitive_keys() -> List[str]:
|
||||||
|
"""
|
||||||
|
Return a list of keys that are sensitive and should not be allowed
|
||||||
|
to be set to hardcoded values in the config. For example, API keys.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def compute_query_embeddings(self, *args, **kwargs) -> list[Union[np.array, None]]:
|
def compute_query_embeddings(self, *args, **kwargs) -> list[Union[np.array, None]]:
|
||||||
@@ -103,20 +159,14 @@ class EmbeddingFunction(BaseModel, ABC):
|
|||||||
return texts
|
return texts
|
||||||
|
|
||||||
def safe_model_dump(self):
|
def safe_model_dump(self):
|
||||||
from ..pydantic import PYDANTIC_VERSION
|
if not hasattr(self, "_original_args"):
|
||||||
|
raise ValueError(
|
||||||
if PYDANTIC_VERSION.major < 2:
|
"EmbeddingFunction was not created with EmbeddingFunction.create()"
|
||||||
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
)
|
||||||
return self.model_dump(
|
return self._original_args
|
||||||
exclude={
|
|
||||||
field_name
|
|
||||||
for field_name in self.model_fields
|
|
||||||
if field_name.startswith("_")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def ndims(self):
|
def ndims(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return the dimensions of the vector column
|
Return the dimensions of the vector column
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ class JinaEmbeddings(EmbeddingFunction):
|
|||||||
# TODO: fix hardcoding
|
# TODO: fix hardcoding
|
||||||
return 768
|
return 768
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sensitive_keys() -> List[str]:
|
||||||
|
return ["api_key"]
|
||||||
|
|
||||||
def sanitize_input(
|
def sanitize_input(
|
||||||
self, inputs: Union[TEXT, IMAGES]
|
self, inputs: Union[TEXT, IMAGES]
|
||||||
) -> Union[List[Any], np.ndarray]:
|
) -> Union[List[Any], np.ndarray]:
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ class OpenAIEmbeddings(TextEmbeddingFunction):
|
|||||||
def ndims(self):
|
def ndims(self):
|
||||||
return self._ndims
|
return self._ndims
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sensitive_keys():
|
||||||
|
return ["api_key"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def model_names():
|
def model_names():
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class EmbeddingFunctionRegistry:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._functions = {}
|
self._functions = {}
|
||||||
|
self._variables = {}
|
||||||
|
|
||||||
def register(self, alias: str = None):
|
def register(self, alias: str = None):
|
||||||
"""
|
"""
|
||||||
@@ -156,6 +157,28 @@ class EmbeddingFunctionRegistry:
|
|||||||
metadata = json.dumps(json_data, indent=2).encode("utf-8")
|
metadata = json.dumps(json_data, indent=2).encode("utf-8")
|
||||||
return {"embedding_functions": metadata}
|
return {"embedding_functions": metadata}
|
||||||
|
|
||||||
|
def set_var(self, name: str, value: str) -> None:
|
||||||
|
"""
|
||||||
|
Set a variable. These can be accessed in embedding configuration using
|
||||||
|
the syntax `$var:variable_name`. If they are not set, an error will be
|
||||||
|
thrown letting you know which variable is missing. If you want to supply
|
||||||
|
a default value, you can add an additional part in the configuration
|
||||||
|
like so: `$var:variable_name:default_value`. Default values can be
|
||||||
|
used for runtime configurations that are not sensitive, such as
|
||||||
|
whether to use a GPU for inference.
|
||||||
|
|
||||||
|
The name must not contain a colon. Default values can contain colons.
|
||||||
|
"""
|
||||||
|
if ":" in name:
|
||||||
|
raise ValueError("Variable names cannot contain colons")
|
||||||
|
self._variables[name] = value
|
||||||
|
|
||||||
|
def get_var(self, name: str) -> str:
|
||||||
|
"""
|
||||||
|
Get a variable.
|
||||||
|
"""
|
||||||
|
return self._variables[name]
|
||||||
|
|
||||||
|
|
||||||
# Global instance
|
# Global instance
|
||||||
__REGISTRY__ = EmbeddingFunctionRegistry()
|
__REGISTRY__ = EmbeddingFunctionRegistry()
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ class WatsonxEmbeddings(TextEmbeddingFunction):
|
|||||||
url: Optional[str] = None
|
url: Optional[str] = None
|
||||||
params: Optional[Dict] = None
|
params: Optional[Dict] = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sensitive_keys():
|
||||||
|
return ["api_key"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def model_names():
|
def model_names():
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -199,18 +199,29 @@ else:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _pydantic_type_to_arrow_type(tp: Any, field: FieldInfo) -> pa.DataType:
|
||||||
|
if inspect.isclass(tp):
|
||||||
|
if issubclass(tp, pydantic.BaseModel):
|
||||||
|
# Struct
|
||||||
|
fields = _pydantic_model_to_fields(tp)
|
||||||
|
return pa.struct(fields)
|
||||||
|
if issubclass(tp, FixedSizeListMixin):
|
||||||
|
return pa.list_(tp.value_arrow_type(), tp.dim())
|
||||||
|
return _py_type_to_arrow_type(tp, field)
|
||||||
|
|
||||||
|
|
||||||
def _pydantic_to_arrow_type(field: FieldInfo) -> pa.DataType:
|
def _pydantic_to_arrow_type(field: FieldInfo) -> pa.DataType:
|
||||||
"""Convert a Pydantic FieldInfo to Arrow DataType"""
|
"""Convert a Pydantic FieldInfo to Arrow DataType"""
|
||||||
|
|
||||||
if isinstance(field.annotation, (_GenericAlias, GenericAlias)):
|
if isinstance(field.annotation, (_GenericAlias, GenericAlias)):
|
||||||
origin = field.annotation.__origin__
|
origin = field.annotation.__origin__
|
||||||
args = field.annotation.__args__
|
args = field.annotation.__args__
|
||||||
|
|
||||||
if origin is list:
|
if origin is list:
|
||||||
child = args[0]
|
child = args[0]
|
||||||
return pa.list_(_py_type_to_arrow_type(child, field))
|
return pa.list_(_py_type_to_arrow_type(child, field))
|
||||||
elif origin == Union:
|
elif origin == Union:
|
||||||
if len(args) == 2 and args[1] is type(None):
|
if len(args) == 2 and args[1] is type(None):
|
||||||
return _py_type_to_arrow_type(args[0], field)
|
return _pydantic_type_to_arrow_type(args[0], field)
|
||||||
elif sys.version_info >= (3, 10) and isinstance(field.annotation, types.UnionType):
|
elif sys.version_info >= (3, 10) and isinstance(field.annotation, types.UnionType):
|
||||||
args = field.annotation.__args__
|
args = field.annotation.__args__
|
||||||
if len(args) == 2:
|
if len(args) == 2:
|
||||||
@@ -218,14 +229,7 @@ def _pydantic_to_arrow_type(field: FieldInfo) -> pa.DataType:
|
|||||||
if typ is type(None):
|
if typ is type(None):
|
||||||
continue
|
continue
|
||||||
return _py_type_to_arrow_type(typ, field)
|
return _py_type_to_arrow_type(typ, field)
|
||||||
elif inspect.isclass(field.annotation):
|
return _pydantic_type_to_arrow_type(field.annotation, field)
|
||||||
if issubclass(field.annotation, pydantic.BaseModel):
|
|
||||||
# Struct
|
|
||||||
fields = _pydantic_model_to_fields(field.annotation)
|
|
||||||
return pa.struct(fields)
|
|
||||||
elif issubclass(field.annotation, FixedSizeListMixin):
|
|
||||||
return pa.list_(field.annotation.value_arrow_type(), field.annotation.dim())
|
|
||||||
return _py_type_to_arrow_type(field.annotation, field)
|
|
||||||
|
|
||||||
|
|
||||||
def is_nullable(field: FieldInfo) -> bool:
|
def is_nullable(field: FieldInfo) -> bool:
|
||||||
@@ -255,7 +259,8 @@ def _pydantic_to_field(name: str, field: FieldInfo) -> pa.Field:
|
|||||||
|
|
||||||
|
|
||||||
def pydantic_to_schema(model: Type[pydantic.BaseModel]) -> pa.Schema:
|
def pydantic_to_schema(model: Type[pydantic.BaseModel]) -> pa.Schema:
|
||||||
"""Convert a Pydantic model to a PyArrow Schema.
|
"""Convert a [Pydantic Model][pydantic.BaseModel] to a
|
||||||
|
[PyArrow Schema][pyarrow.Schema].
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -265,24 +270,25 @@ def pydantic_to_schema(model: Type[pydantic.BaseModel]) -> pa.Schema:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
pyarrow.Schema
|
pyarrow.Schema
|
||||||
|
The Arrow Schema
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
>>> from typing import List, Optional
|
>>> from typing import List, Optional
|
||||||
>>> import pydantic
|
>>> import pydantic
|
||||||
>>> from lancedb.pydantic import pydantic_to_schema
|
>>> from lancedb.pydantic import pydantic_to_schema, Vector
|
||||||
>>> class FooModel(pydantic.BaseModel):
|
>>> class FooModel(pydantic.BaseModel):
|
||||||
... id: int
|
... id: int
|
||||||
... s: str
|
... s: str
|
||||||
... vec: List[float]
|
... vec: Vector(1536) # fixed_size_list<item: float32>[1536]
|
||||||
... li: List[int]
|
... li: List[int]
|
||||||
...
|
...
|
||||||
>>> schema = pydantic_to_schema(FooModel)
|
>>> schema = pydantic_to_schema(FooModel)
|
||||||
>>> assert schema == pa.schema([
|
>>> assert schema == pa.schema([
|
||||||
... pa.field("id", pa.int64(), False),
|
... pa.field("id", pa.int64(), False),
|
||||||
... pa.field("s", pa.utf8(), False),
|
... pa.field("s", pa.utf8(), False),
|
||||||
... pa.field("vec", pa.list_(pa.float64()), False),
|
... pa.field("vec", pa.list_(pa.float32(), 1536)),
|
||||||
... pa.field("li", pa.list_(pa.int64()), False),
|
... pa.field("li", pa.list_(pa.int64()), False),
|
||||||
... ])
|
... ])
|
||||||
"""
|
"""
|
||||||
@@ -304,7 +310,7 @@ class LanceModel(pydantic.BaseModel):
|
|||||||
... vector: Vector(2)
|
... vector: Vector(2)
|
||||||
...
|
...
|
||||||
>>> db = lancedb.connect("./example")
|
>>> db = lancedb.connect("./example")
|
||||||
>>> table = db.create_table("test", schema=TestModel.to_arrow_schema())
|
>>> table = db.create_table("test", schema=TestModel)
|
||||||
>>> table.add([
|
>>> table.add([
|
||||||
... TestModel(name="test", vector=[1.0, 2.0])
|
... TestModel(name="test", vector=[1.0, 2.0])
|
||||||
... ])
|
... ])
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class Query(pydantic.BaseModel):
|
|||||||
full_text_query: Optional[Union[str, dict]] = None
|
full_text_query: Optional[Union[str, dict]] = None
|
||||||
|
|
||||||
# top k results to return
|
# top k results to return
|
||||||
k: int
|
k: Optional[int] = None
|
||||||
|
|
||||||
# # metrics
|
# # metrics
|
||||||
metric: str = "L2"
|
metric: str = "L2"
|
||||||
@@ -257,7 +257,7 @@ class LanceQueryBuilder(ABC):
|
|||||||
|
|
||||||
def __init__(self, table: "Table"):
|
def __init__(self, table: "Table"):
|
||||||
self._table = table
|
self._table = table
|
||||||
self._limit = 10
|
self._limit = None
|
||||||
self._offset = 0
|
self._offset = 0
|
||||||
self._columns = None
|
self._columns = None
|
||||||
self._where = None
|
self._where = None
|
||||||
@@ -370,8 +370,7 @@ class LanceQueryBuilder(ABC):
|
|||||||
The maximum number of results to return.
|
The maximum number of results to return.
|
||||||
The default query limit is 10 results.
|
The default query limit is 10 results.
|
||||||
For ANN/KNN queries, you must specify a limit.
|
For ANN/KNN queries, you must specify a limit.
|
||||||
Entering 0, a negative number, or None will reset
|
For plain searches, all records are returned if limit not set.
|
||||||
the limit to the default value of 10.
|
|
||||||
*WARNING* if you have a large dataset, setting
|
*WARNING* if you have a large dataset, setting
|
||||||
the limit to a large number, e.g. the table size,
|
the limit to a large number, e.g. the table size,
|
||||||
can potentially result in reading a
|
can potentially result in reading a
|
||||||
@@ -595,6 +594,8 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
fast_search: bool = False,
|
fast_search: bool = False,
|
||||||
):
|
):
|
||||||
super().__init__(table)
|
super().__init__(table)
|
||||||
|
if self._limit is None:
|
||||||
|
self._limit = 10
|
||||||
self._query = query
|
self._query = query
|
||||||
self._distance_type = "L2"
|
self._distance_type = "L2"
|
||||||
self._nprobes = 20
|
self._nprobes = 20
|
||||||
@@ -888,6 +889,8 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
fts_columns: Union[str, List[str]] = [],
|
fts_columns: Union[str, List[str]] = [],
|
||||||
):
|
):
|
||||||
super().__init__(table)
|
super().__init__(table)
|
||||||
|
if self._limit is None:
|
||||||
|
self._limit = 10
|
||||||
self._query = query
|
self._query = query
|
||||||
self._phrase_query = False
|
self._phrase_query = False
|
||||||
self.ordering_field_name = ordering_field_name
|
self.ordering_field_name = ordering_field_name
|
||||||
@@ -1055,7 +1058,7 @@ class LanceEmptyQueryBuilder(LanceQueryBuilder):
|
|||||||
query = Query(
|
query = Query(
|
||||||
columns=self._columns,
|
columns=self._columns,
|
||||||
filter=self._where,
|
filter=self._where,
|
||||||
k=self._limit or 10,
|
k=self._limit,
|
||||||
with_row_id=self._with_row_id,
|
with_row_id=self._with_row_id,
|
||||||
vector=[],
|
vector=[],
|
||||||
# not actually respected in remote query
|
# not actually respected in remote query
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class ClientConfig:
|
|||||||
user_agent: str = f"LanceDB-Python-Client/{__version__}"
|
user_agent: str = f"LanceDB-Python-Client/{__version__}"
|
||||||
retry_config: RetryConfig = field(default_factory=RetryConfig)
|
retry_config: RetryConfig = field(default_factory=RetryConfig)
|
||||||
timeout_config: Optional[TimeoutConfig] = field(default_factory=TimeoutConfig)
|
timeout_config: Optional[TimeoutConfig] = field(default_factory=TimeoutConfig)
|
||||||
|
extra_headers: Optional[dict] = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if isinstance(self.retry_config, dict):
|
if isinstance(self.retry_config, dict):
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from typing import Any, Dict, Iterable, List, Optional, Union
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from lancedb import connect_async
|
# Remove this import to fix circular dependency
|
||||||
|
# from lancedb import connect_async
|
||||||
from lancedb.remote import ClientConfig
|
from lancedb.remote import ClientConfig
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
from overrides import override
|
from overrides import override
|
||||||
@@ -78,6 +79,9 @@ class RemoteDBConnection(DBConnection):
|
|||||||
|
|
||||||
self.client_config = client_config
|
self.client_config = client_config
|
||||||
|
|
||||||
|
# Import connect_async here to avoid circular import
|
||||||
|
from lancedb import connect_async
|
||||||
|
|
||||||
self._conn = LOOP.run(
|
self._conn = LOOP.run(
|
||||||
connect_async(
|
connect_async(
|
||||||
db_url,
|
db_url,
|
||||||
|
|||||||
@@ -526,6 +526,9 @@ class RemoteTable(Table):
|
|||||||
def drop_columns(self, columns: Iterable[str]):
|
def drop_columns(self, columns: Iterable[str]):
|
||||||
return LOOP.run(self._table.drop_columns(columns))
|
return LOOP.run(self._table.drop_columns(columns))
|
||||||
|
|
||||||
|
def drop_index(self, index_name: str):
|
||||||
|
return LOOP.run(self._table.drop_index(index_name))
|
||||||
|
|
||||||
def uses_v2_manifest_paths(self) -> bool:
|
def uses_v2_manifest_paths(self) -> bool:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"uses_v2_manifest_paths() is not supported on the LanceDB Cloud"
|
"uses_v2_manifest_paths() is not supported on the LanceDB Cloud"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
28
python/python/lancedb/types.py
Normal file
28
python/python/lancedb/types.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# Query type literals
|
||||||
|
QueryType = Literal["vector", "fts", "hybrid", "auto"]
|
||||||
|
|
||||||
|
# Distance type literals
|
||||||
|
DistanceType = Literal["l2", "cosine", "dot"]
|
||||||
|
DistanceTypeWithHamming = Literal["l2", "cosine", "dot", "hamming"]
|
||||||
|
|
||||||
|
# Vector handling literals
|
||||||
|
OnBadVectorsType = Literal["error", "drop", "fill", "null"]
|
||||||
|
|
||||||
|
# Mode literals
|
||||||
|
AddMode = Literal["append", "overwrite"]
|
||||||
|
CreateMode = Literal["create", "overwrite"]
|
||||||
|
|
||||||
|
# Index type literals
|
||||||
|
VectorIndexType = Literal["IVF_FLAT", "IVF_PQ", "IVF_HNSW_SQ", "IVF_HNSW_PQ"]
|
||||||
|
ScalarIndexType = Literal["BTREE", "BITMAP", "LABEL_LIST"]
|
||||||
|
IndexType = Literal[
|
||||||
|
"IVF_PQ", "IVF_HNSW_PQ", "IVF_HNSW_SQ", "FTS", "BTREE", "BITMAP", "LABEL_LIST"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Tokenizer literals
|
||||||
|
BaseTokenizerType = Literal["simple", "raw", "whitespace"]
|
||||||
@@ -75,6 +75,6 @@ async def test_binary_vector_async():
|
|||||||
|
|
||||||
query = np.random.randint(0, 2, size=256)
|
query = np.random.randint(0, 2, size=256)
|
||||||
packed_query = np.packbits(query)
|
packed_query = np.packbits(query)
|
||||||
await tbl.query().nearest_to(packed_query).distance_type("hamming").to_arrow()
|
await (await tbl.search(packed_query)).distance_type("hamming").to_arrow()
|
||||||
# --8<-- [end:async_binary_vector]
|
# --8<-- [end:async_binary_vector]
|
||||||
await db.drop_table("my_binary_vectors")
|
await db.drop_table("my_binary_vectors")
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ async def test_binary_vector_async():
|
|||||||
query = np.random.random(256)
|
query = np.random.random(256)
|
||||||
|
|
||||||
# Search for the vectors within the range of [0.1, 0.5)
|
# Search for the vectors within the range of [0.1, 0.5)
|
||||||
await tbl.query().nearest_to(query).distance_range(0.1, 0.5).to_arrow()
|
await (await tbl.search(query)).distance_range(0.1, 0.5).to_arrow()
|
||||||
|
|
||||||
# Search for the vectors with the distance less than 0.5
|
# Search for the vectors with the distance less than 0.5
|
||||||
await tbl.query().nearest_to(query).distance_range(upper_bound=0.5).to_arrow()
|
await (await tbl.search(query)).distance_range(upper_bound=0.5).to_arrow()
|
||||||
|
|
||||||
# Search for the vectors with the distance greater or equal to 0.1
|
# Search for the vectors with the distance greater or equal to 0.1
|
||||||
await tbl.query().nearest_to(query).distance_range(lower_bound=0.1).to_arrow()
|
await (await tbl.search(query)).distance_range(lower_bound=0.1).to_arrow()
|
||||||
|
|
||||||
# --8<-- [end:async_distance_range]
|
# --8<-- [end:async_distance_range]
|
||||||
await db.drop_table("my_table")
|
await db.drop_table("my_table")
|
||||||
|
|||||||
@@ -28,3 +28,49 @@ def test_embeddings_openai():
|
|||||||
actual = table.search(query).limit(1).to_pydantic(Words)[0]
|
actual = table.search(query).limit(1).to_pydantic(Words)[0]
|
||||||
print(actual.text)
|
print(actual.text)
|
||||||
# --8<-- [end:openai_embeddings]
|
# --8<-- [end:openai_embeddings]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_embeddings_openai_async():
|
||||||
|
uri = "memory://"
|
||||||
|
# --8<-- [start:async_openai_embeddings]
|
||||||
|
db = await lancedb.connect_async(uri)
|
||||||
|
func = get_registry().get("openai").create(name="text-embedding-ada-002")
|
||||||
|
|
||||||
|
class Words(LanceModel):
|
||||||
|
text: str = func.SourceField()
|
||||||
|
vector: Vector(func.ndims()) = func.VectorField()
|
||||||
|
|
||||||
|
table = await db.create_table("words", schema=Words, mode="overwrite")
|
||||||
|
await table.add([{"text": "hello world"}, {"text": "goodbye world"}])
|
||||||
|
|
||||||
|
query = "greetings"
|
||||||
|
actual = await (await table.search(query)).limit(1).to_pydantic(Words)[0]
|
||||||
|
print(actual.text)
|
||||||
|
# --8<-- [end:async_openai_embeddings]
|
||||||
|
|
||||||
|
|
||||||
|
def test_embeddings_secret():
|
||||||
|
# --8<-- [start:register_secret]
|
||||||
|
registry = get_registry()
|
||||||
|
registry.set_var("api_key", "sk-...")
|
||||||
|
|
||||||
|
func = registry.get("openai").create(api_key="$var:api_key")
|
||||||
|
# --8<-- [end:register_secret]
|
||||||
|
|
||||||
|
try:
|
||||||
|
import torch
|
||||||
|
except ImportError:
|
||||||
|
pytest.skip("torch not installed")
|
||||||
|
|
||||||
|
# --8<-- [start:register_device]
|
||||||
|
import torch
|
||||||
|
|
||||||
|
registry = get_registry()
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
registry.set_var("device", "cuda")
|
||||||
|
|
||||||
|
func = registry.get("huggingface").create(device="$var:device:cpu")
|
||||||
|
# --8<-- [end:register_device]
|
||||||
|
assert func.device == "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ async def test_ann_index_async():
|
|||||||
# --8<-- [end:create_ann_index_async]
|
# --8<-- [end:create_ann_index_async]
|
||||||
# --8<-- [start:vector_search_async]
|
# --8<-- [start:vector_search_async]
|
||||||
await (
|
await (
|
||||||
async_tbl.query()
|
(await async_tbl.search(np.random.random((32))))
|
||||||
.nearest_to(np.random.random((32)))
|
|
||||||
.limit(2)
|
.limit(2)
|
||||||
.nprobes(20)
|
.nprobes(20)
|
||||||
.refine_factor(10)
|
.refine_factor(10)
|
||||||
@@ -82,18 +81,14 @@ async def test_ann_index_async():
|
|||||||
# --8<-- [end:vector_search_async]
|
# --8<-- [end:vector_search_async]
|
||||||
# --8<-- [start:vector_search_async_with_filter]
|
# --8<-- [start:vector_search_async_with_filter]
|
||||||
await (
|
await (
|
||||||
async_tbl.query()
|
(await async_tbl.search(np.random.random((32))))
|
||||||
.nearest_to(np.random.random((32)))
|
|
||||||
.where("item != 'item 1141'")
|
.where("item != 'item 1141'")
|
||||||
.to_pandas()
|
.to_pandas()
|
||||||
)
|
)
|
||||||
# --8<-- [end:vector_search_async_with_filter]
|
# --8<-- [end:vector_search_async_with_filter]
|
||||||
# --8<-- [start:vector_search_async_with_select]
|
# --8<-- [start:vector_search_async_with_select]
|
||||||
await (
|
await (
|
||||||
async_tbl.query()
|
(await async_tbl.search(np.random.random((32)))).select(["vector"]).to_pandas()
|
||||||
.nearest_to(np.random.random((32)))
|
|
||||||
.select(["vector"])
|
|
||||||
.to_pandas()
|
|
||||||
)
|
)
|
||||||
# --8<-- [end:vector_search_async_with_select]
|
# --8<-- [end:vector_search_async_with_select]
|
||||||
|
|
||||||
@@ -164,7 +159,7 @@ async def test_scalar_index_async():
|
|||||||
{"book_id": 3, "vector": [5.0, 6]},
|
{"book_id": 3, "vector": [5.0, 6]},
|
||||||
]
|
]
|
||||||
async_tbl = await async_db.create_table("book_with_embeddings_async", data)
|
async_tbl = await async_db.create_table("book_with_embeddings_async", data)
|
||||||
(await async_tbl.query().where("book_id != 3").nearest_to([1, 2]).to_pandas())
|
(await (await async_tbl.search([1, 2])).where("book_id != 3").to_pandas())
|
||||||
# --8<-- [end:vector_search_with_scalar_index_async]
|
# --8<-- [end:vector_search_with_scalar_index_async]
|
||||||
# --8<-- [start:update_scalar_index_async]
|
# --8<-- [start:update_scalar_index_async]
|
||||||
await async_tbl.add([{"vector": [7, 8], "book_id": 4}])
|
await async_tbl.add([{"vector": [7, 8], "book_id": 4}])
|
||||||
|
|||||||
36
python/python/tests/docs/test_pydantic_integration.py
Normal file
36
python/python/tests/docs/test_pydantic_integration.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
# --8<-- [start:imports]
|
||||||
|
import lancedb
|
||||||
|
from lancedb.pydantic import Vector, LanceModel
|
||||||
|
# --8<-- [end:imports]
|
||||||
|
|
||||||
|
|
||||||
|
def test_pydantic_model(tmp_path):
|
||||||
|
# --8<-- [start:base_model]
|
||||||
|
class PersonModel(LanceModel):
|
||||||
|
name: str
|
||||||
|
age: int
|
||||||
|
vector: Vector(2)
|
||||||
|
|
||||||
|
# --8<-- [end:base_model]
|
||||||
|
|
||||||
|
# --8<-- [start:set_url]
|
||||||
|
url = "./example"
|
||||||
|
# --8<-- [end:set_url]
|
||||||
|
url = tmp_path
|
||||||
|
|
||||||
|
# --8<-- [start:base_example]
|
||||||
|
db = lancedb.connect(url)
|
||||||
|
table = db.create_table("person", schema=PersonModel)
|
||||||
|
table.add(
|
||||||
|
[
|
||||||
|
PersonModel(name="bob", age=1, vector=[1.0, 2.0]),
|
||||||
|
PersonModel(name="alice", age=2, vector=[3.0, 4.0]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert table.count_rows() == 2
|
||||||
|
person = table.search([0.0, 0.0]).limit(1).to_pydantic(PersonModel)
|
||||||
|
assert person[0].name == "bob"
|
||||||
|
# --8<-- [end:base_example]
|
||||||
@@ -126,19 +126,17 @@ async def test_pandas_and_pyarrow_async():
|
|||||||
|
|
||||||
query_vector = [100, 100]
|
query_vector = [100, 100]
|
||||||
# Pandas DataFrame
|
# Pandas DataFrame
|
||||||
df = await async_tbl.query().nearest_to(query_vector).limit(1).to_pandas()
|
df = await (await async_tbl.search(query_vector)).limit(1).to_pandas()
|
||||||
print(df)
|
print(df)
|
||||||
# --8<-- [end:vector_search_async]
|
# --8<-- [end:vector_search_async]
|
||||||
# --8<-- [start:vector_search_with_filter_async]
|
# --8<-- [start:vector_search_with_filter_async]
|
||||||
# Apply the filter via LanceDB
|
# Apply the filter via LanceDB
|
||||||
results = (
|
results = await (await async_tbl.search([100, 100])).where("price < 15").to_pandas()
|
||||||
await async_tbl.query().nearest_to([100, 100]).where("price < 15").to_pandas()
|
|
||||||
)
|
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results["item"].iloc[0] == "foo"
|
assert results["item"].iloc[0] == "foo"
|
||||||
|
|
||||||
# Apply the filter via Pandas
|
# Apply the filter via Pandas
|
||||||
df = results = await async_tbl.query().nearest_to([100, 100]).to_pandas()
|
df = results = await (await async_tbl.search([100, 100])).to_pandas()
|
||||||
results = df[df.price < 15]
|
results = df[df.price < 15]
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results["item"].iloc[0] == "foo"
|
assert results["item"].iloc[0] == "foo"
|
||||||
@@ -188,3 +186,26 @@ def test_polars():
|
|||||||
# --8<-- [start:print_table_lazyform]
|
# --8<-- [start:print_table_lazyform]
|
||||||
print(ldf.first().collect())
|
print(ldf.first().collect())
|
||||||
# --8<-- [end:print_table_lazyform]
|
# --8<-- [end:print_table_lazyform]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_polars_async():
|
||||||
|
uri = "data/sample-lancedb"
|
||||||
|
db = await lancedb.connect_async(uri)
|
||||||
|
|
||||||
|
# --8<-- [start:create_table_polars_async]
|
||||||
|
data = pl.DataFrame(
|
||||||
|
{
|
||||||
|
"vector": [[3.1, 4.1], [5.9, 26.5]],
|
||||||
|
"item": ["foo", "bar"],
|
||||||
|
"price": [10.0, 20.0],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
table = await db.create_table("pl_table_async", data=data)
|
||||||
|
# --8<-- [end:create_table_polars_async]
|
||||||
|
# --8<-- [start:vector_search_polars_async]
|
||||||
|
query = [3.0, 4.0]
|
||||||
|
result = await (await table.search(query)).limit(1).to_polars()
|
||||||
|
print(result)
|
||||||
|
print(type(result))
|
||||||
|
# --8<-- [end:vector_search_polars_async]
|
||||||
|
|||||||
@@ -117,12 +117,11 @@ async def test_vector_search_async():
|
|||||||
for i, row in enumerate(np.random.random((10_000, 1536)).astype("float32"))
|
for i, row in enumerate(np.random.random((10_000, 1536)).astype("float32"))
|
||||||
]
|
]
|
||||||
async_tbl = await async_db.create_table("vector_search_async", data=data)
|
async_tbl = await async_db.create_table("vector_search_async", data=data)
|
||||||
(await async_tbl.query().nearest_to(np.random.random((1536))).limit(10).to_list())
|
(await (await async_tbl.search(np.random.random((1536)))).limit(10).to_list())
|
||||||
# --8<-- [end:exhaustive_search_async]
|
# --8<-- [end:exhaustive_search_async]
|
||||||
# --8<-- [start:exhaustive_search_async_cosine]
|
# --8<-- [start:exhaustive_search_async_cosine]
|
||||||
(
|
(
|
||||||
await async_tbl.query()
|
await (await async_tbl.search(np.random.random((1536))))
|
||||||
.nearest_to(np.random.random((1536)))
|
|
||||||
.distance_type("cosine")
|
.distance_type("cosine")
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.to_list()
|
.to_list()
|
||||||
@@ -145,13 +144,13 @@ async def test_vector_search_async():
|
|||||||
async_tbl = await async_db.create_table("documents_async", data=data)
|
async_tbl = await async_db.create_table("documents_async", data=data)
|
||||||
# --8<-- [end:create_table_async_with_nested_schema]
|
# --8<-- [end:create_table_async_with_nested_schema]
|
||||||
# --8<-- [start:search_result_async_as_pyarrow]
|
# --8<-- [start:search_result_async_as_pyarrow]
|
||||||
await async_tbl.query().nearest_to(np.random.randn(1536)).to_arrow()
|
await (await async_tbl.search(np.random.randn(1536))).to_arrow()
|
||||||
# --8<-- [end:search_result_async_as_pyarrow]
|
# --8<-- [end:search_result_async_as_pyarrow]
|
||||||
# --8<-- [start:search_result_async_as_pandas]
|
# --8<-- [start:search_result_async_as_pandas]
|
||||||
await async_tbl.query().nearest_to(np.random.randn(1536)).to_pandas()
|
await (await async_tbl.search(np.random.randn(1536))).to_pandas()
|
||||||
# --8<-- [end:search_result_async_as_pandas]
|
# --8<-- [end:search_result_async_as_pandas]
|
||||||
# --8<-- [start:search_result_async_as_list]
|
# --8<-- [start:search_result_async_as_list]
|
||||||
await async_tbl.query().nearest_to(np.random.randn(1536)).to_list()
|
await (await async_tbl.search(np.random.randn(1536))).to_list()
|
||||||
# --8<-- [end:search_result_async_as_list]
|
# --8<-- [end:search_result_async_as_list]
|
||||||
|
|
||||||
|
|
||||||
@@ -219,9 +218,7 @@ async def test_fts_native_async():
|
|||||||
|
|
||||||
# async API uses our native FTS algorithm
|
# async API uses our native FTS algorithm
|
||||||
await async_tbl.create_index("text", config=FTS())
|
await async_tbl.create_index("text", config=FTS())
|
||||||
await (
|
await (await async_tbl.search("puppy")).select(["text"]).limit(10).to_list()
|
||||||
async_tbl.query().nearest_to_text("puppy").select(["text"]).limit(10).to_list()
|
|
||||||
)
|
|
||||||
# [{'text': 'Frodo was a happy puppy', '_score': 0.6931471824645996}]
|
# [{'text': 'Frodo was a happy puppy', '_score': 0.6931471824645996}]
|
||||||
# ...
|
# ...
|
||||||
# --8<-- [end:basic_fts_async]
|
# --8<-- [end:basic_fts_async]
|
||||||
@@ -235,18 +232,11 @@ async def test_fts_native_async():
|
|||||||
)
|
)
|
||||||
# --8<-- [end:fts_config_folding_async]
|
# --8<-- [end:fts_config_folding_async]
|
||||||
# --8<-- [start:fts_prefiltering_async]
|
# --8<-- [start:fts_prefiltering_async]
|
||||||
await (
|
await (await async_tbl.search("puppy")).limit(10).where("text='foo'").to_list()
|
||||||
async_tbl.query()
|
|
||||||
.nearest_to_text("puppy")
|
|
||||||
.limit(10)
|
|
||||||
.where("text='foo'")
|
|
||||||
.to_list()
|
|
||||||
)
|
|
||||||
# --8<-- [end:fts_prefiltering_async]
|
# --8<-- [end:fts_prefiltering_async]
|
||||||
# --8<-- [start:fts_postfiltering_async]
|
# --8<-- [start:fts_postfiltering_async]
|
||||||
await (
|
await (
|
||||||
async_tbl.query()
|
(await async_tbl.search("puppy"))
|
||||||
.nearest_to_text("puppy")
|
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.where("text='foo'")
|
.where("text='foo'")
|
||||||
.postfilter()
|
.postfilter()
|
||||||
@@ -347,14 +337,8 @@ async def test_hybrid_search_async():
|
|||||||
# Create a fts index before the hybrid search
|
# Create a fts index before the hybrid search
|
||||||
await async_tbl.create_index("text", config=FTS())
|
await async_tbl.create_index("text", config=FTS())
|
||||||
text_query = "flower moon"
|
text_query = "flower moon"
|
||||||
vector_query = embeddings.compute_query_embeddings(text_query)[0]
|
|
||||||
# hybrid search with default re-ranker
|
# hybrid search with default re-ranker
|
||||||
await (
|
await (await async_tbl.search("flower moon", query_type="hybrid")).to_pandas()
|
||||||
async_tbl.query()
|
|
||||||
.nearest_to(vector_query)
|
|
||||||
.nearest_to_text(text_query)
|
|
||||||
.to_pandas()
|
|
||||||
)
|
|
||||||
# --8<-- [end:basic_hybrid_search_async]
|
# --8<-- [end:basic_hybrid_search_async]
|
||||||
# --8<-- [start:hybrid_search_pass_vector_text_async]
|
# --8<-- [start:hybrid_search_pass_vector_text_async]
|
||||||
vector_query = [0.1, 0.2, 0.3, 0.4, 0.5]
|
vector_query = [0.1, 0.2, 0.3, 0.4, 0.5]
|
||||||
|
|||||||
@@ -299,12 +299,12 @@ def test_create_exist_ok(tmp_db: lancedb.DBConnection):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_connect(tmp_path):
|
async def test_connect(tmp_path):
|
||||||
db = await lancedb.connect_async(tmp_path)
|
db = await lancedb.connect_async(tmp_path)
|
||||||
assert str(db) == f"NativeDatabase(uri={tmp_path}, read_consistency_interval=None)"
|
assert str(db) == f"ListingDatabase(uri={tmp_path}, read_consistency_interval=None)"
|
||||||
|
|
||||||
db = await lancedb.connect_async(
|
db = await lancedb.connect_async(
|
||||||
tmp_path, read_consistency_interval=timedelta(seconds=5)
|
tmp_path, read_consistency_interval=timedelta(seconds=5)
|
||||||
)
|
)
|
||||||
assert str(db) == f"NativeDatabase(uri={tmp_path}, read_consistency_interval=5s)"
|
assert str(db) == f"ListingDatabase(uri={tmp_path}, read_consistency_interval=5s)"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -396,13 +396,16 @@ async def test_create_exist_ok_async(tmp_db_async: lancedb.AsyncConnection):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_table_v2_manifest_paths_async(tmp_path):
|
async def test_create_table_v2_manifest_paths_async(tmp_path):
|
||||||
db = await lancedb.connect_async(tmp_path)
|
db_with_v2_paths = await lancedb.connect_async(
|
||||||
|
tmp_path, storage_options={"new_table_enable_v2_manifest_paths": "true"}
|
||||||
|
)
|
||||||
|
db_no_v2_paths = await lancedb.connect_async(
|
||||||
|
tmp_path, storage_options={"new_table_enable_v2_manifest_paths": "false"}
|
||||||
|
)
|
||||||
# Create table in v2 mode with v2 manifest paths enabled
|
# Create table in v2 mode with v2 manifest paths enabled
|
||||||
tbl = await db.create_table(
|
tbl = await db_with_v2_paths.create_table(
|
||||||
"test_v2_manifest_paths",
|
"test_v2_manifest_paths",
|
||||||
data=[{"id": 0}],
|
data=[{"id": 0}],
|
||||||
use_legacy_format=False,
|
|
||||||
enable_v2_manifest_paths=True,
|
|
||||||
)
|
)
|
||||||
assert await tbl.uses_v2_manifest_paths()
|
assert await tbl.uses_v2_manifest_paths()
|
||||||
manifests_dir = tmp_path / "test_v2_manifest_paths.lance" / "_versions"
|
manifests_dir = tmp_path / "test_v2_manifest_paths.lance" / "_versions"
|
||||||
@@ -410,11 +413,9 @@ async def test_create_table_v2_manifest_paths_async(tmp_path):
|
|||||||
assert re.match(r"\d{20}\.manifest", manifest)
|
assert re.match(r"\d{20}\.manifest", manifest)
|
||||||
|
|
||||||
# Start a table in V1 mode then migrate
|
# Start a table in V1 mode then migrate
|
||||||
tbl = await db.create_table(
|
tbl = await db_no_v2_paths.create_table(
|
||||||
"test_v2_migration",
|
"test_v2_migration",
|
||||||
data=[{"id": 0}],
|
data=[{"id": 0}],
|
||||||
use_legacy_format=False,
|
|
||||||
enable_v2_manifest_paths=False,
|
|
||||||
)
|
)
|
||||||
assert not await tbl.uses_v2_manifest_paths()
|
assert not await tbl.uses_v2_manifest_paths()
|
||||||
manifests_dir = tmp_path / "test_v2_migration.lance" / "_versions"
|
manifests_dir = tmp_path / "test_v2_migration.lance" / "_versions"
|
||||||
@@ -498,6 +499,10 @@ def test_delete_table(tmp_db: lancedb.DBConnection):
|
|||||||
# if ignore_missing=True
|
# if ignore_missing=True
|
||||||
tmp_db.drop_table("does_not_exist", ignore_missing=True)
|
tmp_db.drop_table("does_not_exist", ignore_missing=True)
|
||||||
|
|
||||||
|
tmp_db.drop_all_tables()
|
||||||
|
|
||||||
|
assert tmp_db.table_names() == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_delete_table_async(tmp_db: lancedb.DBConnection):
|
async def test_delete_table_async(tmp_db: lancedb.DBConnection):
|
||||||
@@ -583,7 +588,7 @@ def test_empty_or_nonexistent_table(mem_db: lancedb.DBConnection):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_in_v2_mode(mem_db_async: lancedb.AsyncConnection):
|
async def test_create_in_v2_mode():
|
||||||
def make_data():
|
def make_data():
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
yield pa.record_batch([pa.array([x for x in range(1024)])], names=["x"])
|
yield pa.record_batch([pa.array([x for x in range(1024)])], names=["x"])
|
||||||
@@ -594,10 +599,13 @@ async def test_create_in_v2_mode(mem_db_async: lancedb.AsyncConnection):
|
|||||||
schema = pa.schema([pa.field("x", pa.int64())])
|
schema = pa.schema([pa.field("x", pa.int64())])
|
||||||
|
|
||||||
# Create table in v1 mode
|
# Create table in v1 mode
|
||||||
tbl = await mem_db_async.create_table(
|
|
||||||
"test", data=make_data(), schema=schema, data_storage_version="legacy"
|
v1_db = await lancedb.connect_async(
|
||||||
|
"memory://", storage_options={"new_table_data_storage_version": "legacy"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tbl = await v1_db.create_table("test", data=make_data(), schema=schema)
|
||||||
|
|
||||||
async def is_in_v2_mode(tbl):
|
async def is_in_v2_mode(tbl):
|
||||||
batches = (
|
batches = (
|
||||||
await tbl.query().limit(10 * 1024).to_batches(max_batch_length=1024 * 10)
|
await tbl.query().limit(10 * 1024).to_batches(max_batch_length=1024 * 10)
|
||||||
@@ -610,10 +618,12 @@ async def test_create_in_v2_mode(mem_db_async: lancedb.AsyncConnection):
|
|||||||
assert not await is_in_v2_mode(tbl)
|
assert not await is_in_v2_mode(tbl)
|
||||||
|
|
||||||
# Create table in v2 mode
|
# Create table in v2 mode
|
||||||
tbl = await mem_db_async.create_table(
|
v2_db = await lancedb.connect_async(
|
||||||
"test_v2", data=make_data(), schema=schema, use_legacy_format=False
|
"memory://", storage_options={"new_table_data_storage_version": "stable"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tbl = await v2_db.create_table("test_v2", data=make_data(), schema=schema)
|
||||||
|
|
||||||
assert await is_in_v2_mode(tbl)
|
assert await is_in_v2_mode(tbl)
|
||||||
|
|
||||||
# Add data (should remain in v2 mode)
|
# Add data (should remain in v2 mode)
|
||||||
@@ -622,20 +632,18 @@ async def test_create_in_v2_mode(mem_db_async: lancedb.AsyncConnection):
|
|||||||
assert await is_in_v2_mode(tbl)
|
assert await is_in_v2_mode(tbl)
|
||||||
|
|
||||||
# Create empty table in v2 mode and add data
|
# Create empty table in v2 mode and add data
|
||||||
tbl = await mem_db_async.create_table(
|
tbl = await v2_db.create_table("test_empty_v2", data=None, schema=schema)
|
||||||
"test_empty_v2", data=None, schema=schema, use_legacy_format=False
|
|
||||||
)
|
|
||||||
await tbl.add(make_table())
|
await tbl.add(make_table())
|
||||||
|
|
||||||
assert await is_in_v2_mode(tbl)
|
assert await is_in_v2_mode(tbl)
|
||||||
|
|
||||||
# Create empty table uses v1 mode by default
|
# Db uses v2 mode by default
|
||||||
tbl = await mem_db_async.create_table(
|
db = await lancedb.connect_async("memory://")
|
||||||
"test_empty_v2_default", data=None, schema=schema, data_storage_version="legacy"
|
|
||||||
)
|
tbl = await db.create_table("test_empty_v2_default", data=None, schema=schema)
|
||||||
await tbl.add(make_table())
|
await tbl.add(make_table())
|
||||||
|
|
||||||
assert not await is_in_v2_mode(tbl)
|
assert await is_in_v2_mode(tbl)
|
||||||
|
|
||||||
|
|
||||||
def test_replace_index(mem_db: lancedb.DBConnection):
|
def test_replace_index(mem_db: lancedb.DBConnection):
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
from typing import List, Union
|
import os
|
||||||
|
from typing import List, Optional, Union
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import lance
|
import lance
|
||||||
@@ -56,7 +57,7 @@ def test_embedding_function(tmp_path):
|
|||||||
conf = EmbeddingFunctionConfig(
|
conf = EmbeddingFunctionConfig(
|
||||||
source_column="text",
|
source_column="text",
|
||||||
vector_column="vector",
|
vector_column="vector",
|
||||||
function=MockTextEmbeddingFunction(),
|
function=MockTextEmbeddingFunction.create(),
|
||||||
)
|
)
|
||||||
metadata = registry.get_table_metadata([conf])
|
metadata = registry.get_table_metadata([conf])
|
||||||
table = table.replace_schema_metadata(metadata)
|
table = table.replace_schema_metadata(metadata)
|
||||||
@@ -80,6 +81,57 @@ def test_embedding_function(tmp_path):
|
|||||||
assert np.allclose(actual, expected)
|
assert np.allclose(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedding_function_variables():
|
||||||
|
@register("variable-testing")
|
||||||
|
class VariableTestingFunction(TextEmbeddingFunction):
|
||||||
|
key1: str
|
||||||
|
secret_key: Optional[str] = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sensitive_keys():
|
||||||
|
return ["secret_key"]
|
||||||
|
|
||||||
|
def ndims():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def generate_embeddings(self, _texts):
|
||||||
|
pass
|
||||||
|
|
||||||
|
registry = EmbeddingFunctionRegistry.get_instance()
|
||||||
|
|
||||||
|
# Should error if variable is not set
|
||||||
|
with pytest.raises(ValueError, match="Variable 'test' not found"):
|
||||||
|
registry.get("variable-testing").create(
|
||||||
|
key1="$var:test",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should use default values if not set
|
||||||
|
func = registry.get("variable-testing").create(key1="$var:test:some_value")
|
||||||
|
assert func.key1 == "some_value"
|
||||||
|
|
||||||
|
# Should set a variable that the embedding function understands
|
||||||
|
registry.set_var("test", "some_value")
|
||||||
|
func = registry.get("variable-testing").create(key1="$var:test")
|
||||||
|
assert func.key1 == "some_value"
|
||||||
|
|
||||||
|
# Should reject secrets that aren't passed in as variables
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match="Sensitive key 'secret_key' cannot be set to a hardcoded value",
|
||||||
|
):
|
||||||
|
registry.get("variable-testing").create(
|
||||||
|
key1="whatever", secret_key="some_value"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should not serialize secrets.
|
||||||
|
registry.set_var("secret", "secret_value")
|
||||||
|
func = registry.get("variable-testing").create(
|
||||||
|
key1="whatever", secret_key="$var:secret"
|
||||||
|
)
|
||||||
|
assert func.secret_key == "secret_value"
|
||||||
|
assert func.safe_model_dump()["secret_key"] == "$var:secret"
|
||||||
|
|
||||||
|
|
||||||
def test_embedding_with_bad_results(tmp_path):
|
def test_embedding_with_bad_results(tmp_path):
|
||||||
@register("null-embedding")
|
@register("null-embedding")
|
||||||
class NullEmbeddingFunction(TextEmbeddingFunction):
|
class NullEmbeddingFunction(TextEmbeddingFunction):
|
||||||
@@ -91,9 +143,11 @@ def test_embedding_with_bad_results(tmp_path):
|
|||||||
) -> list[Union[np.array, None]]:
|
) -> list[Union[np.array, None]]:
|
||||||
# Return None, which is bad if field is non-nullable
|
# Return None, which is bad if field is non-nullable
|
||||||
a = [
|
a = [
|
||||||
np.full(self.ndims(), np.nan)
|
(
|
||||||
if i % 2 == 0
|
np.full(self.ndims(), np.nan)
|
||||||
else np.random.randn(self.ndims())
|
if i % 2 == 0
|
||||||
|
else np.random.randn(self.ndims())
|
||||||
|
)
|
||||||
for i in range(len(texts))
|
for i in range(len(texts))
|
||||||
]
|
]
|
||||||
return a
|
return a
|
||||||
@@ -107,7 +161,7 @@ def test_embedding_with_bad_results(tmp_path):
|
|||||||
vector: Vector(model.ndims()) = model.VectorField()
|
vector: Vector(model.ndims()) = model.VectorField()
|
||||||
|
|
||||||
table = db.create_table("test", schema=Schema, mode="overwrite")
|
table = db.create_table("test", schema=Schema, mode="overwrite")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(RuntimeError):
|
||||||
# Default on_bad_vectors is "error"
|
# Default on_bad_vectors is "error"
|
||||||
table.add([{"text": "hello world"}])
|
table.add([{"text": "hello world"}])
|
||||||
|
|
||||||
@@ -341,6 +395,7 @@ def test_add_optional_vector(tmp_path):
|
|||||||
assert not (np.abs(tbl.to_pandas()["vector"][0]) < 1e-6).all()
|
assert not (np.abs(tbl.to_pandas()["vector"][0]) < 1e-6).all()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"embedding_type",
|
"embedding_type",
|
||||||
[
|
[
|
||||||
@@ -358,23 +413,23 @@ def test_embedding_function_safe_model_dump(embedding_type):
|
|||||||
|
|
||||||
# Note: Some embedding types might require specific parameters
|
# Note: Some embedding types might require specific parameters
|
||||||
try:
|
try:
|
||||||
model = registry.get(embedding_type).create()
|
model = registry.get(embedding_type).create({"max_retries": 1})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pytest.skip(f"Skipping {embedding_type} due to error: {str(e)}")
|
pytest.skip(f"Skipping {embedding_type} due to error: {str(e)}")
|
||||||
|
|
||||||
dumped_model = model.safe_model_dump()
|
dumped_model = model.safe_model_dump()
|
||||||
|
|
||||||
assert all(
|
assert all(not k.startswith("_") for k in dumped_model.keys()), (
|
||||||
not k.startswith("_") for k in dumped_model.keys()
|
f"{embedding_type}: Dumped model contains keys starting with underscore"
|
||||||
), f"{embedding_type}: Dumped model contains keys starting with underscore"
|
)
|
||||||
|
|
||||||
assert (
|
assert "max_retries" in dumped_model, (
|
||||||
"max_retries" in dumped_model
|
f"{embedding_type}: Essential field 'max_retries' is missing from dumped model"
|
||||||
), f"{embedding_type}: Essential field 'max_retries' is missing from dumped model"
|
)
|
||||||
|
|
||||||
assert isinstance(
|
assert isinstance(dumped_model, dict), (
|
||||||
dumped_model, dict
|
f"{embedding_type}: Dumped model is not a dictionary"
|
||||||
), f"{embedding_type}: Dumped model is not a dictionary"
|
)
|
||||||
|
|
||||||
for key in model.__dict__:
|
for key in model.__dict__:
|
||||||
if key.startswith("_"):
|
if key.startswith("_"):
|
||||||
@@ -391,3 +446,33 @@ def test_retry(mock_sleep):
|
|||||||
result = test_function()
|
result = test_function()
|
||||||
assert mock_sleep.call_count == 9
|
assert mock_sleep.call_count == 9
|
||||||
assert result == "result"
|
assert result == "result"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.environ.get("OPENAI_API_KEY") is None, reason="OpenAI API key not set"
|
||||||
|
)
|
||||||
|
def test_openai_propagates_api_key(monkeypatch):
|
||||||
|
# Make sure that if we set it as a variable, the API key is propagated
|
||||||
|
api_key = os.environ["OPENAI_API_KEY"]
|
||||||
|
monkeypatch.delenv("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
uri = "memory://"
|
||||||
|
registry = get_registry()
|
||||||
|
registry.set_var("open_api_key", api_key)
|
||||||
|
func = registry.get("openai").create(
|
||||||
|
name="text-embedding-ada-002",
|
||||||
|
max_retries=0,
|
||||||
|
api_key="$var:open_api_key",
|
||||||
|
)
|
||||||
|
|
||||||
|
class Words(LanceModel):
|
||||||
|
text: str = func.SourceField()
|
||||||
|
vector: Vector(func.ndims()) = func.VectorField()
|
||||||
|
|
||||||
|
db = lancedb.connect(uri)
|
||||||
|
table = db.create_table("words", schema=Words, mode="overwrite")
|
||||||
|
table.add([{"text": "hello world"}, {"text": "goodbye world"}])
|
||||||
|
|
||||||
|
query = "greetings"
|
||||||
|
actual = table.search(query).limit(1).to_pydantic(Words)[0]
|
||||||
|
assert len(actual.text) > 0
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ def test_search_fts(table, use_tantivy):
|
|||||||
assert len(results) == 5
|
assert len(results) == 5
|
||||||
assert len(results[0]) == 3 # id, text, _score
|
assert len(results[0]) == 3 # id, text, _score
|
||||||
|
|
||||||
|
# Default limit of 10
|
||||||
|
results = table.search("puppy").select(["id", "text"]).to_list()
|
||||||
|
assert len(results) == 10
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fts_select_async(async_table):
|
async def test_fts_select_async(async_table):
|
||||||
|
|||||||
@@ -129,6 +129,6 @@ def test_normalize_scores():
|
|||||||
if invert:
|
if invert:
|
||||||
expected = pc.subtract(1.0, expected)
|
expected = pc.subtract(1.0, expected)
|
||||||
|
|
||||||
assert pc.equal(
|
assert pc.equal(result, expected), (
|
||||||
result, expected
|
f"Expected {expected} but got {result} for invert={invert}"
|
||||||
), f"Expected {expected} but got {result} for invert={invert}"
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import pyarrow as pa
|
|||||||
import pydantic
|
import pydantic
|
||||||
import pytest
|
import pytest
|
||||||
from lancedb.pydantic import PYDANTIC_VERSION, LanceModel, Vector, pydantic_to_schema
|
from lancedb.pydantic import PYDANTIC_VERSION, LanceModel, Vector, pydantic_to_schema
|
||||||
|
from pydantic import BaseModel
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
|
|
||||||
@@ -252,3 +253,104 @@ def test_lance_model():
|
|||||||
|
|
||||||
t = TestModel()
|
t = TestModel()
|
||||||
assert t == TestModel(vec=[0.0] * 16, li=[1, 2, 3])
|
assert t == TestModel(vec=[0.0] * 16, li=[1, 2, 3])
|
||||||
|
|
||||||
|
|
||||||
|
def test_optional_nested_model():
|
||||||
|
class WAMedia(BaseModel):
|
||||||
|
url: str
|
||||||
|
mimetype: str
|
||||||
|
filename: Optional[str]
|
||||||
|
error: Optional[str]
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
class WALocation(BaseModel):
|
||||||
|
description: Optional[str]
|
||||||
|
latitude: str
|
||||||
|
longitude: str
|
||||||
|
|
||||||
|
class ReplyToMessage(BaseModel):
|
||||||
|
id: str
|
||||||
|
participant: str
|
||||||
|
body: str
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
id: str
|
||||||
|
timestamp: int
|
||||||
|
from_: str
|
||||||
|
fromMe: bool
|
||||||
|
to: str
|
||||||
|
body: str
|
||||||
|
hasMedia: Optional[bool]
|
||||||
|
media: WAMedia
|
||||||
|
mediaUrl: Optional[str]
|
||||||
|
ack: Optional[int]
|
||||||
|
ackName: Optional[str]
|
||||||
|
author: Optional[str]
|
||||||
|
location: Optional[WALocation]
|
||||||
|
vCards: Optional[List[str]]
|
||||||
|
replyTo: Optional[ReplyToMessage]
|
||||||
|
|
||||||
|
class AnyEvent(LanceModel):
|
||||||
|
id: str
|
||||||
|
session: str
|
||||||
|
metadata: Optional[str] = None
|
||||||
|
engine: str
|
||||||
|
event: str
|
||||||
|
|
||||||
|
class MessageEvent(AnyEvent):
|
||||||
|
payload: Message
|
||||||
|
|
||||||
|
schema = pydantic_to_schema(MessageEvent)
|
||||||
|
|
||||||
|
payload = schema.field("payload")
|
||||||
|
assert payload.type == pa.struct(
|
||||||
|
[
|
||||||
|
pa.field("id", pa.utf8(), False),
|
||||||
|
pa.field("timestamp", pa.int64(), False),
|
||||||
|
pa.field("from_", pa.utf8(), False),
|
||||||
|
pa.field("fromMe", pa.bool_(), False),
|
||||||
|
pa.field("to", pa.utf8(), False),
|
||||||
|
pa.field("body", pa.utf8(), False),
|
||||||
|
pa.field("hasMedia", pa.bool_(), True),
|
||||||
|
pa.field(
|
||||||
|
"media",
|
||||||
|
pa.struct(
|
||||||
|
[
|
||||||
|
pa.field("url", pa.utf8(), False),
|
||||||
|
pa.field("mimetype", pa.utf8(), False),
|
||||||
|
pa.field("filename", pa.utf8(), True),
|
||||||
|
pa.field("error", pa.utf8(), True),
|
||||||
|
pa.field("data", pa.binary(), False),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
pa.field("mediaUrl", pa.utf8(), True),
|
||||||
|
pa.field("ack", pa.int64(), True),
|
||||||
|
pa.field("ackName", pa.utf8(), True),
|
||||||
|
pa.field("author", pa.utf8(), True),
|
||||||
|
pa.field(
|
||||||
|
"location",
|
||||||
|
pa.struct(
|
||||||
|
[
|
||||||
|
pa.field("description", pa.utf8(), True),
|
||||||
|
pa.field("latitude", pa.utf8(), False),
|
||||||
|
pa.field("longitude", pa.utf8(), False),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
True, # Optional
|
||||||
|
),
|
||||||
|
pa.field("vCards", pa.list_(pa.utf8()), True),
|
||||||
|
pa.field(
|
||||||
|
"replyTo",
|
||||||
|
pa.struct(
|
||||||
|
[
|
||||||
|
pa.field("id", pa.utf8(), False),
|
||||||
|
pa.field("participant", pa.utf8(), False),
|
||||||
|
pa.field("body", pa.utf8(), False),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,25 +1,35 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
from typing import List, Union
|
||||||
import unittest.mock as mock
|
import unittest.mock as mock
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import lancedb
|
import lancedb
|
||||||
from lancedb.index import IvfPq, FTS
|
from lancedb.db import AsyncConnection
|
||||||
from lancedb.rerankers.cross_encoder import CrossEncoderReranker
|
from lancedb.embeddings.base import TextEmbeddingFunction
|
||||||
|
from lancedb.embeddings.registry import get_registry, register
|
||||||
|
from lancedb.index import FTS, IvfPq
|
||||||
|
import lancedb.pydantic
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas.testing as tm
|
import pandas.testing as tm
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
|
import pyarrow.compute as pc
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
from lancedb.pydantic import LanceModel, Vector
|
from lancedb.pydantic import LanceModel, Vector
|
||||||
from lancedb.query import (
|
from lancedb.query import (
|
||||||
|
AsyncFTSQuery,
|
||||||
|
AsyncHybridQuery,
|
||||||
AsyncQueryBase,
|
AsyncQueryBase,
|
||||||
|
AsyncVectorQuery,
|
||||||
LanceVectorQueryBuilder,
|
LanceVectorQueryBuilder,
|
||||||
Query,
|
Query,
|
||||||
)
|
)
|
||||||
|
from lancedb.rerankers.cross_encoder import CrossEncoderReranker
|
||||||
from lancedb.table import AsyncTable, LanceTable
|
from lancedb.table import AsyncTable, LanceTable
|
||||||
|
from utils import exception_output
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
@@ -232,6 +242,71 @@ async def test_distance_range_async(table_async: AsyncTable):
|
|||||||
assert res["_distance"].to_pylist() == [min_dist, max_dist]
|
assert res["_distance"].to_pylist() == [min_dist, max_dist]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_distance_range_with_new_rows_async():
|
||||||
|
conn = await lancedb.connect_async(
|
||||||
|
"memory://", read_consistency_interval=timedelta(seconds=0)
|
||||||
|
)
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"vector": pa.FixedShapeTensorArray.from_numpy_ndarray(
|
||||||
|
np.random.rand(256, 2)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
table = await conn.create_table("test", data)
|
||||||
|
table.create_index("vector", config=IvfPq(num_partitions=1, num_sub_vectors=2))
|
||||||
|
|
||||||
|
q = [0, 0]
|
||||||
|
rs = await table.query().nearest_to(q).to_arrow()
|
||||||
|
dists = rs["_distance"].to_pylist()
|
||||||
|
min_dist = dists[0]
|
||||||
|
max_dist = dists[-1]
|
||||||
|
|
||||||
|
# append more rows so that execution plan would be mixed with ANN & Flat KNN
|
||||||
|
new_data = pa.table(
|
||||||
|
{
|
||||||
|
"vector": pa.FixedShapeTensorArray.from_numpy_ndarray(np.random.rand(4, 2)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await table.add(new_data)
|
||||||
|
|
||||||
|
res = (
|
||||||
|
await table.query()
|
||||||
|
.nearest_to(q)
|
||||||
|
.distance_range(upper_bound=min_dist)
|
||||||
|
.to_arrow()
|
||||||
|
)
|
||||||
|
assert len(res) == 0
|
||||||
|
|
||||||
|
res = (
|
||||||
|
await table.query()
|
||||||
|
.nearest_to(q)
|
||||||
|
.distance_range(lower_bound=max_dist)
|
||||||
|
.to_arrow()
|
||||||
|
)
|
||||||
|
for dist in res["_distance"].to_pylist():
|
||||||
|
assert dist >= max_dist
|
||||||
|
|
||||||
|
res = (
|
||||||
|
await table.query()
|
||||||
|
.nearest_to(q)
|
||||||
|
.distance_range(upper_bound=max_dist)
|
||||||
|
.to_arrow()
|
||||||
|
)
|
||||||
|
for dist in res["_distance"].to_pylist():
|
||||||
|
assert dist < max_dist
|
||||||
|
|
||||||
|
res = (
|
||||||
|
await table.query()
|
||||||
|
.nearest_to(q)
|
||||||
|
.distance_range(lower_bound=min_dist)
|
||||||
|
.to_arrow()
|
||||||
|
)
|
||||||
|
for dist in res["_distance"].to_pylist():
|
||||||
|
assert dist >= min_dist
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"multivec_table", [pa.float16(), pa.float32(), pa.float64()], indirect=True
|
"multivec_table", [pa.float16(), pa.float32(), pa.float64()], indirect=True
|
||||||
)
|
)
|
||||||
@@ -651,3 +726,100 @@ async def test_query_with_f16(tmp_path: Path):
|
|||||||
tbl = await db.create_table("test", df)
|
tbl = await db.create_table("test", df)
|
||||||
results = await tbl.vector_search([np.float16(1), np.float16(2)]).to_pandas()
|
results = await tbl.vector_search([np.float16(1), np.float16(2)]).to_pandas()
|
||||||
assert len(results) == 2
|
assert len(results) == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_query_search_auto(mem_db_async: AsyncConnection):
|
||||||
|
nrows = 1000
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"text": [str(i) for i in range(nrows)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@register("test2")
|
||||||
|
class TestEmbedding(TextEmbeddingFunction):
|
||||||
|
def ndims(self):
|
||||||
|
return 4
|
||||||
|
|
||||||
|
def generate_embeddings(
|
||||||
|
self, texts: Union[List[str], np.ndarray]
|
||||||
|
) -> List[np.array]:
|
||||||
|
embeddings = []
|
||||||
|
for text in texts:
|
||||||
|
vec = np.array([float(text) / 1000] * self.ndims())
|
||||||
|
embeddings.append(vec)
|
||||||
|
return embeddings
|
||||||
|
|
||||||
|
registry = get_registry()
|
||||||
|
func = registry.get("test2").create()
|
||||||
|
|
||||||
|
class TestModel(LanceModel):
|
||||||
|
text: str = func.SourceField()
|
||||||
|
vector: Vector(func.ndims()) = func.VectorField()
|
||||||
|
|
||||||
|
tbl = await mem_db_async.create_table("test", data, schema=TestModel)
|
||||||
|
|
||||||
|
funcs = await tbl.embedding_functions()
|
||||||
|
assert len(funcs) == 1
|
||||||
|
|
||||||
|
# No FTS or vector index
|
||||||
|
# Search for vector -> vector query
|
||||||
|
q = [0.1] * 4
|
||||||
|
query = await tbl.search(q)
|
||||||
|
assert isinstance(query, AsyncVectorQuery)
|
||||||
|
|
||||||
|
# Search for string -> vector query
|
||||||
|
query = await tbl.search("0.1")
|
||||||
|
assert isinstance(query, AsyncVectorQuery)
|
||||||
|
|
||||||
|
await tbl.create_index("text", config=FTS())
|
||||||
|
|
||||||
|
query = await tbl.search("0.1")
|
||||||
|
assert isinstance(query, AsyncHybridQuery)
|
||||||
|
|
||||||
|
data_with_vecs = await tbl.to_arrow()
|
||||||
|
data_with_vecs = data_with_vecs.replace_schema_metadata(None)
|
||||||
|
tbl2 = await mem_db_async.create_table("test2", data_with_vecs)
|
||||||
|
with pytest.raises(
|
||||||
|
Exception,
|
||||||
|
match=(
|
||||||
|
"Cannot perform full text search unless an INVERTED index has been created"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
query = await (await tbl2.search("0.1")).to_arrow()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_query_search_specified(mem_db_async: AsyncConnection):
|
||||||
|
nrows, ndims = 1000, 16
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"text": [str(i) for i in range(nrows)],
|
||||||
|
"vector": pa.FixedSizeListArray.from_arrays(
|
||||||
|
pc.random(nrows * ndims).cast(pa.float32()), ndims
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
table = await mem_db_async.create_table("test", data)
|
||||||
|
await table.create_index("text", config=FTS())
|
||||||
|
|
||||||
|
# Validate that specifying fts, vector or hybrid gets the right query.
|
||||||
|
q = [0.1] * ndims
|
||||||
|
query = await table.search(q, query_type="vector")
|
||||||
|
assert isinstance(query, AsyncVectorQuery)
|
||||||
|
|
||||||
|
query = await table.search("0.1", query_type="fts")
|
||||||
|
assert isinstance(query, AsyncFTSQuery)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Unknown query type: 'foo'"):
|
||||||
|
await table.search("0.1", query_type="foo")
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match="Column 'vector' has no registered embedding function"
|
||||||
|
) as e:
|
||||||
|
await table.search("0.1", query_type="vector")
|
||||||
|
|
||||||
|
assert "No embedding functions are registered for any columns" in exception_output(
|
||||||
|
e
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import json
|
|||||||
import threading
|
import threading
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
import uuid
|
import uuid
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
import lancedb
|
import lancedb
|
||||||
from lancedb.conftest import MockTextEmbeddingFunction
|
from lancedb.conftest import MockTextEmbeddingFunction
|
||||||
@@ -32,15 +33,16 @@ def make_mock_http_handler(handler):
|
|||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def mock_lancedb_connection(handler):
|
def mock_lancedb_connection(handler):
|
||||||
with http.server.HTTPServer(
|
with http.server.HTTPServer(
|
||||||
("localhost", 8080), make_mock_http_handler(handler)
|
("localhost", 0), make_mock_http_handler(handler)
|
||||||
) as server:
|
) as server:
|
||||||
|
port = server.server_address[1]
|
||||||
handle = threading.Thread(target=server.serve_forever)
|
handle = threading.Thread(target=server.serve_forever)
|
||||||
handle.start()
|
handle.start()
|
||||||
|
|
||||||
db = lancedb.connect(
|
db = lancedb.connect(
|
||||||
"db://dev",
|
"db://dev",
|
||||||
api_key="fake",
|
api_key="fake",
|
||||||
host_override="http://localhost:8080",
|
host_override=f"http://localhost:{port}",
|
||||||
client_config={
|
client_config={
|
||||||
"retry_config": {"retries": 2},
|
"retry_config": {"retries": 2},
|
||||||
"timeout_config": {
|
"timeout_config": {
|
||||||
@@ -57,22 +59,24 @@ def mock_lancedb_connection(handler):
|
|||||||
|
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def mock_lancedb_connection_async(handler):
|
async def mock_lancedb_connection_async(handler, **client_config):
|
||||||
with http.server.HTTPServer(
|
with http.server.HTTPServer(
|
||||||
("localhost", 8080), make_mock_http_handler(handler)
|
("localhost", 0), make_mock_http_handler(handler)
|
||||||
) as server:
|
) as server:
|
||||||
|
port = server.server_address[1]
|
||||||
handle = threading.Thread(target=server.serve_forever)
|
handle = threading.Thread(target=server.serve_forever)
|
||||||
handle.start()
|
handle.start()
|
||||||
|
|
||||||
db = await lancedb.connect_async(
|
db = await lancedb.connect_async(
|
||||||
"db://dev",
|
"db://dev",
|
||||||
api_key="fake",
|
api_key="fake",
|
||||||
host_override="http://localhost:8080",
|
host_override=f"http://localhost:{port}",
|
||||||
client_config={
|
client_config={
|
||||||
"retry_config": {"retries": 2},
|
"retry_config": {"retries": 2},
|
||||||
"timeout_config": {
|
"timeout_config": {
|
||||||
"connect_timeout": 1,
|
"connect_timeout": 1,
|
||||||
},
|
},
|
||||||
|
**client_config,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -254,6 +258,9 @@ def test_table_create_indices():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
request.wfile.write(payload.encode())
|
request.wfile.write(payload.encode())
|
||||||
|
elif "/drop/" in request.path:
|
||||||
|
request.send_response(200)
|
||||||
|
request.end_headers()
|
||||||
else:
|
else:
|
||||||
request.send_response(404)
|
request.send_response(404)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
@@ -265,14 +272,18 @@ def test_table_create_indices():
|
|||||||
table.create_scalar_index("id")
|
table.create_scalar_index("id")
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text")
|
||||||
table.create_scalar_index("vector")
|
table.create_scalar_index("vector")
|
||||||
|
table.drop_index("vector_idx")
|
||||||
|
table.drop_index("id_idx")
|
||||||
|
table.drop_index("text_idx")
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def query_test_table(query_handler):
|
def query_test_table(query_handler, *, server_version=Version("0.1.0")):
|
||||||
def handler(request):
|
def handler(request):
|
||||||
if request.path == "/v1/table/test/describe/":
|
if request.path == "/v1/table/test/describe/":
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.send_header("Content-Type", "application/json")
|
request.send_header("Content-Type", "application/json")
|
||||||
|
request.send_header("phalanx-version", str(server_version))
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
request.wfile.write(b"{}")
|
request.wfile.write(b"{}")
|
||||||
elif request.path == "/v1/table/test/query/":
|
elif request.path == "/v1/table/test/query/":
|
||||||
@@ -329,6 +340,7 @@ def test_query_sync_empty_query():
|
|||||||
"filter": "true",
|
"filter": "true",
|
||||||
"vector": [],
|
"vector": [],
|
||||||
"columns": ["id"],
|
"columns": ["id"],
|
||||||
|
"prefilter": False,
|
||||||
"version": None,
|
"version": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,11 +390,25 @@ def test_query_sync_maximal():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_query_sync_multiple_vectors():
|
@pytest.mark.parametrize("server_version", [Version("0.1.0"), Version("0.2.0")])
|
||||||
def handler(_body):
|
def test_query_sync_batch_queries(server_version):
|
||||||
return pa.table({"id": [1]})
|
def handler(body):
|
||||||
|
# TODO: we will add the ability to get the server version,
|
||||||
|
# so that we can decide how to perform batch quires.
|
||||||
|
vectors = body["vector"]
|
||||||
|
if server_version >= Version(
|
||||||
|
"0.2.0"
|
||||||
|
): # we can handle batch queries in single request since 0.2.0
|
||||||
|
assert len(vectors) == 2
|
||||||
|
res = []
|
||||||
|
for i, vector in enumerate(vectors):
|
||||||
|
res.append({"id": 1, "query_index": i})
|
||||||
|
return pa.Table.from_pylist(res)
|
||||||
|
else:
|
||||||
|
assert len(vectors) == 3 # matching dim
|
||||||
|
return pa.table({"id": [1]})
|
||||||
|
|
||||||
with query_test_table(handler) as table:
|
with query_test_table(handler, server_version=server_version) as table:
|
||||||
results = table.search([[1, 2, 3], [4, 5, 6]]).limit(1).to_list()
|
results = table.search([[1, 2, 3], [4, 5, 6]]).limit(1).to_list()
|
||||||
assert len(results) == 2
|
assert len(results) == 2
|
||||||
results.sort(key=lambda x: x["query_index"])
|
results.sort(key=lambda x: x["query_index"])
|
||||||
@@ -397,6 +423,7 @@ def test_query_sync_fts():
|
|||||||
"columns": [],
|
"columns": [],
|
||||||
},
|
},
|
||||||
"k": 10,
|
"k": 10,
|
||||||
|
"prefilter": True,
|
||||||
"vector": [],
|
"vector": [],
|
||||||
"version": None,
|
"version": None,
|
||||||
}
|
}
|
||||||
@@ -414,6 +441,7 @@ def test_query_sync_fts():
|
|||||||
},
|
},
|
||||||
"k": 42,
|
"k": 42,
|
||||||
"vector": [],
|
"vector": [],
|
||||||
|
"prefilter": True,
|
||||||
"with_row_id": True,
|
"with_row_id": True,
|
||||||
"version": None,
|
"version": None,
|
||||||
}
|
}
|
||||||
@@ -440,6 +468,7 @@ def test_query_sync_hybrid():
|
|||||||
},
|
},
|
||||||
"k": 42,
|
"k": 42,
|
||||||
"vector": [],
|
"vector": [],
|
||||||
|
"prefilter": True,
|
||||||
"with_row_id": True,
|
"with_row_id": True,
|
||||||
"version": None,
|
"version": None,
|
||||||
}
|
}
|
||||||
@@ -522,3 +551,19 @@ def test_create_client():
|
|||||||
|
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
lancedb.connect(**mandatory_args, request_thread_pool=10)
|
lancedb.connect(**mandatory_args, request_thread_pool=10)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pass_through_headers():
|
||||||
|
def handler(request):
|
||||||
|
assert request.headers["foo"] == "bar"
|
||||||
|
request.send_response(200)
|
||||||
|
request.send_header("Content-Type", "application/json")
|
||||||
|
request.end_headers()
|
||||||
|
request.wfile.write(b'{"tables": []}')
|
||||||
|
|
||||||
|
async with mock_lancedb_connection_async(
|
||||||
|
handler, extra_headers={"foo": "bar"}
|
||||||
|
) as db:
|
||||||
|
table_names = await db.table_names()
|
||||||
|
assert table_names == []
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ pytest.importorskip("lancedb.fts")
|
|||||||
def get_test_table(tmp_path, use_tantivy):
|
def get_test_table(tmp_path, use_tantivy):
|
||||||
db = lancedb.connect(tmp_path)
|
db = lancedb.connect(tmp_path)
|
||||||
# Create a LanceDB table schema with a vector and a text column
|
# Create a LanceDB table schema with a vector and a text column
|
||||||
emb = EmbeddingFunctionRegistry.get_instance().get("test")()
|
emb = EmbeddingFunctionRegistry.get_instance().get("test").create()
|
||||||
meta_emb = EmbeddingFunctionRegistry.get_instance().get("test")()
|
meta_emb = EmbeddingFunctionRegistry.get_instance().get("test").create()
|
||||||
|
|
||||||
class MyTable(LanceModel):
|
class MyTable(LanceModel):
|
||||||
text: str = emb.SourceField()
|
text: str = emb.SourceField()
|
||||||
@@ -131,9 +131,9 @@ def _run_test_reranker(reranker, table, query, query_vector, schema):
|
|||||||
"represents the relevance of the result to the query & should "
|
"represents the relevance of the result to the query & should "
|
||||||
"be descending."
|
"be descending."
|
||||||
)
|
)
|
||||||
assert np.all(
|
assert np.all(np.diff(result.column("_relevance_score").to_numpy()) <= 0), (
|
||||||
np.diff(result.column("_relevance_score").to_numpy()) <= 0
|
ascending_relevance_err
|
||||||
), ascending_relevance_err
|
)
|
||||||
|
|
||||||
# Vector search setting
|
# Vector search setting
|
||||||
result = (
|
result = (
|
||||||
@@ -143,9 +143,9 @@ def _run_test_reranker(reranker, table, query, query_vector, schema):
|
|||||||
.to_arrow()
|
.to_arrow()
|
||||||
)
|
)
|
||||||
assert len(result) == 30
|
assert len(result) == 30
|
||||||
assert np.all(
|
assert np.all(np.diff(result.column("_relevance_score").to_numpy()) <= 0), (
|
||||||
np.diff(result.column("_relevance_score").to_numpy()) <= 0
|
ascending_relevance_err
|
||||||
), ascending_relevance_err
|
)
|
||||||
result_explicit = (
|
result_explicit = (
|
||||||
table.search(query_vector, vector_column_name="vector")
|
table.search(query_vector, vector_column_name="vector")
|
||||||
.rerank(reranker=reranker, query_string=query)
|
.rerank(reranker=reranker, query_string=query)
|
||||||
@@ -168,9 +168,9 @@ def _run_test_reranker(reranker, table, query, query_vector, schema):
|
|||||||
.to_arrow()
|
.to_arrow()
|
||||||
)
|
)
|
||||||
assert len(result) > 0
|
assert len(result) > 0
|
||||||
assert np.all(
|
assert np.all(np.diff(result.column("_relevance_score").to_numpy()) <= 0), (
|
||||||
np.diff(result.column("_relevance_score").to_numpy()) <= 0
|
ascending_relevance_err
|
||||||
), ascending_relevance_err
|
)
|
||||||
|
|
||||||
# empty FTS results
|
# empty FTS results
|
||||||
query = "abcxyz" * 100
|
query = "abcxyz" * 100
|
||||||
@@ -185,9 +185,9 @@ def _run_test_reranker(reranker, table, query, query_vector, schema):
|
|||||||
|
|
||||||
# should return _relevance_score column
|
# should return _relevance_score column
|
||||||
assert "_relevance_score" in result.column_names
|
assert "_relevance_score" in result.column_names
|
||||||
assert np.all(
|
assert np.all(np.diff(result.column("_relevance_score").to_numpy()) <= 0), (
|
||||||
np.diff(result.column("_relevance_score").to_numpy()) <= 0
|
ascending_relevance_err
|
||||||
), ascending_relevance_err
|
)
|
||||||
|
|
||||||
# Multi-vector search setting
|
# Multi-vector search setting
|
||||||
rs1 = table.search(query, vector_column_name="vector").limit(10).with_row_id(True)
|
rs1 = table.search(query, vector_column_name="vector").limit(10).with_row_id(True)
|
||||||
@@ -262,9 +262,9 @@ def _run_test_hybrid_reranker(reranker, tmp_path, use_tantivy):
|
|||||||
"represents the relevance of the result to the query & should "
|
"represents the relevance of the result to the query & should "
|
||||||
"be descending."
|
"be descending."
|
||||||
)
|
)
|
||||||
assert np.all(
|
assert np.all(np.diff(result.column("_relevance_score").to_numpy()) <= 0), (
|
||||||
np.diff(result.column("_relevance_score").to_numpy()) <= 0
|
ascending_relevance_err
|
||||||
), ascending_relevance_err
|
)
|
||||||
|
|
||||||
# Test with empty FTS results
|
# Test with empty FTS results
|
||||||
query = "abcxyz" * 100
|
query = "abcxyz" * 100
|
||||||
@@ -278,9 +278,9 @@ def _run_test_hybrid_reranker(reranker, tmp_path, use_tantivy):
|
|||||||
)
|
)
|
||||||
# should return _relevance_score column
|
# should return _relevance_score column
|
||||||
assert "_relevance_score" in result.column_names
|
assert "_relevance_score" in result.column_names
|
||||||
assert np.all(
|
assert np.all(np.diff(result.column("_relevance_score").to_numpy()) <= 0), (
|
||||||
np.diff(result.column("_relevance_score").to_numpy()) <= 0
|
ascending_relevance_err
|
||||||
), ascending_relevance_err
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("use_tantivy", [True, False])
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
@@ -405,7 +405,9 @@ def test_answerdotai_reranker(tmp_path, use_tantivy):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
os.environ.get("OPENAI_API_KEY") is None, reason="OPENAI_API_KEY not set"
|
os.environ.get("OPENAI_API_KEY") is None
|
||||||
|
or os.environ.get("OPENAI_BASE_URL") is not None,
|
||||||
|
reason="OPENAI_API_KEY not set",
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("use_tantivy", [True, False])
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
def test_openai_reranker(tmp_path, use_tantivy):
|
def test_openai_reranker(tmp_path, use_tantivy):
|
||||||
|
|||||||
@@ -252,3 +252,27 @@ def test_s3_dynamodb_sync(s3_bucket: str, commit_table: str, monkeypatch):
|
|||||||
db.drop_table("test_ddb_sync")
|
db.drop_table("test_ddb_sync")
|
||||||
assert db.table_names() == []
|
assert db.table_names() == []
|
||||||
db.drop_database()
|
db.drop_database()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.s3_test
|
||||||
|
def test_s3_dynamodb_drop_all_tables(s3_bucket: str, commit_table: str, monkeypatch):
|
||||||
|
for key, value in CONFIG.items():
|
||||||
|
monkeypatch.setenv(key.upper(), value)
|
||||||
|
|
||||||
|
uri = f"s3+ddb://{s3_bucket}/test2?ddbTableName={commit_table}"
|
||||||
|
db = lancedb.connect(uri, read_consistency_interval=timedelta(0))
|
||||||
|
data = pa.table({"x": ["a", "b", "c"]})
|
||||||
|
|
||||||
|
db.create_table("foo", data)
|
||||||
|
db.create_table("bar", data)
|
||||||
|
assert db.table_names() == ["bar", "foo"]
|
||||||
|
|
||||||
|
# dropping all tables should clear multiple tables
|
||||||
|
db.drop_all_tables()
|
||||||
|
assert db.table_names() == []
|
||||||
|
|
||||||
|
# create a new table with the same name to ensure DDB is clean
|
||||||
|
db.create_table("foo", data)
|
||||||
|
assert db.table_names() == ["foo"]
|
||||||
|
|
||||||
|
db.drop_all_tables()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user