mirror of
https://github.com/lancedb/lancedb.git
synced 2026-01-04 19:02:58 +00:00
feat: infer vector columns when name contains 'vector' or 'embedding' (#2547)
## Summary - Enhanced vector column detection to use substring matching instead of exact matching - Now detects columns with names containing "vector" or "embedding" (case-insensitive) - Added integer vector support to Node.js implementation (matching Python) - Comprehensive test coverage for both float and integer vector types ## Changes ### Python (`python/python/lancedb/table.py`) - Updated `_infer_target_schema()` to use substring matching with helper function `_is_vector_column()` - Preserved original field names instead of forcing "vector" - Consolidated duplicate logic for better maintainability ### Node.js (`nodejs/lancedb/arrow.ts`) - Enhanced type inference with `nameSuggestsVectorColumn()` helper function - Added `isAllIntegers()` function with performance optimization (checks first 10 elements) - Implemented integer vector support using `Uint8` type (matching Python) - Improved type safety by removing `any` usage ### Tests - **Python**: Added `test_infer_target_schema_with_vector_embedding_names()` in `test_util.py` - **Node.js**: Added comprehensive test case in `arrow.test.ts` - Both test suites cover various naming patterns and integer/float vector types ## Examples of newly supported column names: - `user_vector`, `text_embedding`, `doc_embeddings` - `my_vector_field`, `embedding_model` - `VECTOR_COL`, `Vector_Mixed` (case-insensitive) - Both float and integer arrays are properly converted to fixed-size lists ## Test plan - [x] All existing tests pass (backward compatibility maintained) - [x] New tests pass for both Python and Node.js implementations - [x] Integer vector detection works correctly in Node.js - [x] Code passes linting and formatting checks - [x] Performance optimized for large vector arrays Fixes #2546 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||
|
||||
import { Bool, Field, Int32, List, Schema, Struct, Utf8 } from "apache-arrow";
|
||||
import {
|
||||
Bool,
|
||||
Field,
|
||||
Int32,
|
||||
List,
|
||||
Schema,
|
||||
Struct,
|
||||
Uint8,
|
||||
Utf8,
|
||||
} from "apache-arrow";
|
||||
|
||||
import * as arrow15 from "apache-arrow-15";
|
||||
import * as arrow16 from "apache-arrow-16";
|
||||
@@ -255,6 +264,98 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
||||
expect(actualSchema).toEqual(schema);
|
||||
});
|
||||
|
||||
it("will detect vector columns when name contains 'vector' or 'embedding'", async function () {
|
||||
// Test various naming patterns that should be detected as vector columns
|
||||
const floatVectorTable = makeArrowTable([
|
||||
{
|
||||
// Float vectors (use decimal values to ensure they're treated as floats)
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
user_vector: [1.1, 2.2],
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
text_embedding: [3.3, 4.4],
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
doc_embeddings: [5.5, 6.6],
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
my_vector_field: [7.7, 8.8],
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
embedding_model: [9.9, 10.1],
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
VECTOR_COL: [11.1, 12.2], // uppercase
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
Vector_Mixed: [13.3, 14.4], // mixed case
|
||||
},
|
||||
]);
|
||||
|
||||
// Check that columns with 'vector' or 'embedding' in name are converted to FixedSizeList
|
||||
const floatVectorColumns = [
|
||||
"user_vector",
|
||||
"text_embedding",
|
||||
"doc_embeddings",
|
||||
"my_vector_field",
|
||||
"embedding_model",
|
||||
"VECTOR_COL",
|
||||
"Vector_Mixed",
|
||||
];
|
||||
|
||||
for (const columnName of floatVectorColumns) {
|
||||
expect(
|
||||
DataType.isFixedSizeList(
|
||||
floatVectorTable.getChild(columnName)?.type,
|
||||
),
|
||||
).toBe(true);
|
||||
// Check that float vectors use Float32 by default
|
||||
expect(
|
||||
floatVectorTable
|
||||
.getChild(columnName)
|
||||
?.type.children[0].type.toString(),
|
||||
).toEqual(new Float32().toString());
|
||||
}
|
||||
|
||||
// Test that regular integer arrays still get treated as float vectors
|
||||
// (since JavaScript doesn't distinguish integers from floats at runtime)
|
||||
const integerArrayTable = makeArrowTable([
|
||||
{
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
vector_int: [1, 2], // Regular array with integers - should be Float32
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
embedding_int: [3, 4], // Regular array with integers - should be Float32
|
||||
},
|
||||
]);
|
||||
|
||||
const integerArrayColumns = ["vector_int", "embedding_int"];
|
||||
|
||||
for (const columnName of integerArrayColumns) {
|
||||
expect(
|
||||
DataType.isFixedSizeList(
|
||||
integerArrayTable.getChild(columnName)?.type,
|
||||
),
|
||||
).toBe(true);
|
||||
// Regular integer arrays should use Float32 (avoiding false positives)
|
||||
expect(
|
||||
integerArrayTable
|
||||
.getChild(columnName)
|
||||
?.type.children[0].type.toString(),
|
||||
).toEqual(new Float32().toString());
|
||||
}
|
||||
|
||||
// Test normal list should NOT be converted to FixedSizeList
|
||||
const normalListTable = makeArrowTable([
|
||||
{
|
||||
// biome-ignore lint/style/useNamingConvention: Testing vector column detection patterns
|
||||
normal_list: [15.5, 16.6], // should NOT be detected as vector
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
DataType.isFixedSizeList(
|
||||
normalListTable.getChild("normal_list")?.type,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
DataType.isList(normalListTable.getChild("normal_list")?.type),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("will allow different vector column types", async function () {
|
||||
const table = makeArrowTable([{ fp16: [1], fp32: [1], fp64: [1] }], {
|
||||
vectorColumns: {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Struct,
|
||||
Timestamp,
|
||||
Type,
|
||||
Uint8,
|
||||
Utf8,
|
||||
Vector,
|
||||
makeVector as arrowMakeVector,
|
||||
@@ -51,6 +52,15 @@ import {
|
||||
sanitizeTable,
|
||||
sanitizeType,
|
||||
} from "./sanitize";
|
||||
|
||||
/**
|
||||
* Check if a field name indicates a vector column.
|
||||
*/
|
||||
function nameSuggestsVectorColumn(fieldName: string): boolean {
|
||||
const nameLower = fieldName.toLowerCase();
|
||||
return nameLower.includes("vector") || nameLower.includes("embedding");
|
||||
}
|
||||
|
||||
export * from "apache-arrow";
|
||||
export type SchemaLike =
|
||||
| Schema
|
||||
@@ -591,10 +601,17 @@ function inferType(
|
||||
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);
|
||||
if (nameSuggestsVectorColumn(path[path.length - 1])) {
|
||||
// Check if value is a Uint8Array for integer vector type determination
|
||||
if (value instanceof Uint8Array) {
|
||||
// For integer vectors, we default to Uint8 (matching Python implementation)
|
||||
const child = new Field("item", new Uint8(), true);
|
||||
return new FixedSizeList(value.length, child);
|
||||
} else {
|
||||
// For float vectors, we default to Float32
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user