From 89c6df86dccdde6f6dbc7e47c90e26bf83fd88c5 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 1 Jun 2025 15:55:36 +0200 Subject: [PATCH 1/9] feat: add method for adding named! vectors --- src/collections/config/index.ts | 10 +- src/collections/config/integration.test.ts | 21 ++++ src/collections/config/utils.ts | 101 +++++++++++++++++- src/collections/configure/types/vectorizer.ts | 4 + src/schema/vectorAdder.ts | 56 ++++++++++ 5 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/schema/vectorAdder.ts diff --git a/src/collections/config/index.ts b/src/collections/config/index.ts index 320545c7..70a21799 100644 --- a/src/collections/config/index.ts +++ b/src/collections/config/index.ts @@ -4,11 +4,13 @@ import { WeaviateShardStatus } from '../../openapi/types.js'; import ClassUpdater from '../../schema/classUpdater.js'; import { ClassGetter, PropertyCreator, ShardUpdater } from '../../schema/index.js'; import ShardsGetter from '../../schema/shardsGetter.js'; +import VectorAdder from '../../schema/vectorAdder.js'; import { DbVersionSupport } from '../../utils/dbVersion.js'; import { PropertyConfigCreate, ReferenceMultiTargetConfigCreate, ReferenceSingleTargetConfigCreate, + VectorizersConfigAdd, } from '../configure/types/index.js'; import { MergeWithExisting } from './classes.js'; import { @@ -23,7 +25,7 @@ import { VectorIndexConfigFlat, VectorIndexConfigHNSW, } from './types/index.js'; -import { classToCollection, resolveProperty, resolveReference } from './utils.js'; +import { classToCollection, makeVectorsConfig, resolveProperty, resolveReference } from './utils.js'; const config = ( connection: Connection, @@ -47,6 +49,11 @@ const config = ( .withProperty(resolveReference(reference)) .do() .then(() => {}), + addVector: async (vectors: VectorizersConfigAdd) => { + const supportsDynamicVectorIndex = await dbVersionSupport.supportsDynamicVectorIndex(); + const { vectorsConfig } = makeVectorsConfig(vectors, supportsDynamicVectorIndex); + return new VectorAdder(connection).withClassName(name).withVectors(vectorsConfig).do(); + }, get: () => getRaw().then(classToCollection), getShards: () => { let builder = new ShardsGetter(connection).withClassName(name); @@ -114,6 +121,7 @@ export interface Config { addReference: ( reference: ReferenceSingleTargetConfigCreate | ReferenceMultiTargetConfigCreate ) => Promise; + addVector: (vectors: VectorizersConfigAdd) => Promise; /** * Get the configuration for this collection from Weaviate. * diff --git a/src/collections/config/integration.test.ts b/src/collections/config/integration.test.ts index be729ea0..9cc08c6d 100644 --- a/src/collections/config/integration.test.ts +++ b/src/collections/config/integration.test.ts @@ -386,6 +386,27 @@ describe('Testing of the collection.config namespace', () => { ]); }); + it('should be able to add a reference to a collection', async () => { + const collectionName = 'TestCollectionConfigAddVector' as const; + const collection = await client.collections.create({ + name: collectionName, + vectorizers: [weaviate.configure.vectorizer.none({ name: 'original' })], + }); + // Add a single named vector + await collection.config.addVector(weaviate.configure.vectorizer.none({ name: 'vector-a' })); + + // Add several named vectors + await collection.config.addVector([ + weaviate.configure.vectorizer.none({ name: 'vector-b' }), + weaviate.configure.vectorizer.none({ name: 'vector-c' }), + ]); + + const config = await collection.config.get(); + expect(config.vectorizers).toHaveProperty('vector-a'); + expect(config.vectorizers).toHaveProperty('vector-b'); + expect(config.vectorizers).toHaveProperty('vector-c'); + }); + it('should get the shards of a sharded collection', async () => { const shards = await client.collections .create({ diff --git a/src/collections/config/utils.ts b/src/collections/config/utils.ts index da6ab9d6..a66a1313 100644 --- a/src/collections/config/utils.ts +++ b/src/collections/config/utils.ts @@ -1,5 +1,10 @@ -import { WeaviateDeserializationError } from '../../errors.js'; import { + WeaviateDeserializationError, + WeaviateInvalidInputError, + WeaviateUnsupportedFeatureError, +} from '../../errors.js'; +import { + Properties, WeaviateBM25Config, WeaviateClass, WeaviateInvertedIndexConfig, @@ -13,11 +18,19 @@ import { WeaviateVectorIndexConfig, WeaviateVectorsConfig, } from '../../openapi/types.js'; +import { DbVersionSupport } from '../../utils/dbVersion.js'; +import { QuantizerGuards } from '../configure/parsing.js'; import { PropertyConfigCreate, ReferenceConfigCreate, ReferenceMultiTargetConfigCreate, ReferenceSingleTargetConfigCreate, + VectorIndexConfigCreate, + VectorIndexConfigDynamicCreate, + VectorIndexConfigFlatCreate, + VectorIndexConfigHNSWCreate, + VectorizersConfigAdd, + VectorizersConfigCreate, } from '../configure/types/index.js'; import { BQConfig, @@ -46,6 +59,7 @@ import { VectorIndexConfigHNSW, VectorIndexConfigType, VectorIndexFilterStrategy, + VectorIndexType, VectorizerConfig, } from './types/index.js'; @@ -123,6 +137,91 @@ export const classToCollection = (cls: WeaviateClass): CollectionConfig => { }; }; +export const parseVectorIndex = (module: ModuleConfig): any => { + if (module.config === undefined) return undefined; + if (module.name === 'dynamic') { + const { hnsw, flat, ...conf } = module.config as VectorIndexConfigDynamicCreate; + return { + ...conf, + hnsw: parseVectorIndex({ name: 'hnsw', config: hnsw }), + flat: parseVectorIndex({ name: 'flat', config: flat }), + }; + } + const { quantizer, ...conf } = module.config as + | VectorIndexConfigFlatCreate + | VectorIndexConfigHNSWCreate + | Record; + if (quantizer === undefined) return conf; + if (QuantizerGuards.isBQCreate(quantizer)) { + const { type, ...quant } = quantizer; + return { + ...conf, + bq: { + ...quant, + enabled: true, + }, + }; + } + if (QuantizerGuards.isPQCreate(quantizer)) { + const { type, ...quant } = quantizer; + return { + ...conf, + pq: { + ...quant, + enabled: true, + }, + }; + } +}; + +export const parseVectorizerConfig = (config?: VectorizerConfig): any => { + if (config === undefined) return {}; + const { vectorizeCollectionName, ...rest } = config as any; + return { + ...rest, + vectorizeClassName: vectorizeCollectionName, + }; +}; + +export const makeVectorsConfig = ( + configVectorizers: VectorizersConfigCreate | VectorizersConfigAdd, + supportsDynamicVectorIndex: Awaited> +) => { + let vectorizers: string[] = []; + const vectorsConfig: Record = {}; + const vectorizersConfig = Array.isArray(configVectorizers) + ? configVectorizers + : [ + { + ...configVectorizers, + name: configVectorizers.name || 'default', + }, + ]; + vectorizersConfig.forEach((v) => { + if (v.vectorIndex.name === 'dynamic' && !supportsDynamicVectorIndex.supports) { + throw new WeaviateUnsupportedFeatureError(supportsDynamicVectorIndex.message); + } + const vectorConfig: any = { + vectorIndexConfig: parseVectorIndex(v.vectorIndex), + vectorIndexType: v.vectorIndex.name, + vectorizer: {}, + }; + const vectorizer = v.vectorizer.name === 'text2vec-azure-openai' ? 'text2vec-openai' : v.vectorizer.name; + vectorizers = [...vectorizers, vectorizer]; + vectorConfig.vectorizer[vectorizer] = { + properties: v.properties, + ...parseVectorizerConfig(v.vectorizer.config), + }; + if (v.name === undefined) { + throw new WeaviateInvalidInputError( + 'vectorName is required for each vectorizer when specifying more than one vectorizer' + ); + } + vectorsConfig[v.name] = vectorConfig; + }); + return { vectorsConfig, vectorizers }; +}; + function populated(v: T | null | undefined): v is T { return v !== undefined && v !== null; } diff --git a/src/collections/configure/types/vectorizer.ts b/src/collections/configure/types/vectorizer.ts index 5391c356..4de07cbf 100644 --- a/src/collections/configure/types/vectorizer.ts +++ b/src/collections/configure/types/vectorizer.ts @@ -58,6 +58,10 @@ export type VectorizersConfigCreate = | VectorConfigCreate, undefined, VectorIndexType, Vectorizer> | VectorConfigCreate, string, VectorIndexType, Vectorizer>[]; +export type VectorizersConfigAdd = + | VectorConfigCreate, string, VectorIndexType, Vectorizer> + | VectorConfigCreate, string, VectorIndexType, Vectorizer>[]; + export type ConfigureNonTextVectorizerOptions< N extends string | undefined, I extends VectorIndexType, diff --git a/src/schema/vectorAdder.ts b/src/schema/vectorAdder.ts new file mode 100644 index 00000000..cb3f164f --- /dev/null +++ b/src/schema/vectorAdder.ts @@ -0,0 +1,56 @@ +import Connection from '../connection/index.js'; +import { VectorConfig } from '../index.js'; +import { CommandBase } from '../validation/commandBase.js'; +import { isValidStringProperty } from '../validation/string.js'; +import ClassGetter from './classGetter.js'; + +export default class VectorAdder extends CommandBase { + private className!: string; + private vectors!: Record; + + constructor(client: Connection) { + super(client); + } + + withClassName = (className: string) => { + this.className = className; + return this; + }; + withVectors = (vectors: Record) => { + this.vectors = vectors; + return this; + }; + + validateClassName = () => { + if (!isValidStringProperty(this.className)) { + this.addError('className must be set - set with .withClassName(className)'); + } + }; + + validate = () => { + this.validateClassName(); + }; + + do = (): Promise => { + this.validate(); + if (this.errors.length > 0) { + return Promise.reject(new Error('invalid usage: ' + this.errors.join(', '))); + } + + return new ClassGetter(this.client) + .withClassName(this.className) + .do() + .then(async (schema) => { + if (schema.vectorConfig === undefined) { + schema.vectorConfig = {}; + } + + for (const [key, value] of Object.entries(this.vectors)) { + schema.vectorConfig![key] = { ...value }; + } + + const path = `/schema/${this.className}`; + await this.client.put(path, schema); + }); + }; +} From d709622caf4675c67647c8e87de2ff9896d29850 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 1 Jun 2025 16:12:40 +0200 Subject: [PATCH 2/9] refactor: import utils --- src/collections/index.ts | 103 ++++----------------------------------- 1 file changed, 10 insertions(+), 93 deletions(-) diff --git a/src/collections/index.ts b/src/collections/index.ts index b5c2edb5..9eb7b52e 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -1,12 +1,18 @@ import Connection from '../connection/grpc.js'; -import { WeaviateInvalidInputError, WeaviateUnsupportedFeatureError } from '../errors.js'; +import { WeaviateUnsupportedFeatureError } from '../errors.js'; import { WeaviateClass } from '../openapi/types.js'; import ClassExists from '../schema/classExists.js'; import { ClassCreator, ClassDeleter, ClassGetter, SchemaGetter } from '../schema/index.js'; import { DbVersionSupport } from '../utils/dbVersion.js'; import collection, { Collection } from './collection/index.js'; -import { classToCollection, resolveProperty, resolveReference } from './config/utils.js'; -import { QuantizerGuards } from './configure/parsing.js'; +import { + classToCollection, + makeVectorsConfig, + parseVectorIndex, + parseVectorizerConfig, + resolveProperty, + resolveReference, +} from './config/utils.js'; import { configGuards } from './index.js'; import { CollectionConfig, @@ -23,13 +29,7 @@ import { RerankerConfig, ShardingConfigCreate, VectorConfigCreate, - VectorIndexConfigCreate, - VectorIndexConfigDynamicCreate, - VectorIndexConfigFlatCreate, - VectorIndexConfigHNSWCreate, - VectorIndexType, Vectorizer, - VectorizerConfig, VectorizersConfigCreate, } from './types/index.js'; import { PrimitiveKeys } from './types/internal.js'; @@ -65,52 +65,6 @@ export type CollectionConfigCreate = { vectorizers?: VectorizersConfigCreate; }; -const parseVectorIndex = (module: ModuleConfig): any => { - if (module.config === undefined) return undefined; - if (module.name === 'dynamic') { - const { hnsw, flat, ...conf } = module.config as VectorIndexConfigDynamicCreate; - return { - ...conf, - hnsw: parseVectorIndex({ name: 'hnsw', config: hnsw }), - flat: parseVectorIndex({ name: 'flat', config: flat }), - }; - } - const { quantizer, ...conf } = module.config as - | VectorIndexConfigFlatCreate - | VectorIndexConfigHNSWCreate - | Record; - if (quantizer === undefined) return conf; - if (QuantizerGuards.isBQCreate(quantizer)) { - const { type, ...quant } = quantizer; - return { - ...conf, - bq: { - ...quant, - enabled: true, - }, - }; - } - if (QuantizerGuards.isPQCreate(quantizer)) { - const { type, ...quant } = quantizer; - return { - ...conf, - pq: { - ...quant, - enabled: true, - }, - }; - } -}; - -const parseVectorizerConfig = (config?: VectorizerConfig): any => { - if (config === undefined) return {}; - const { vectorizeCollectionName, ...rest } = config as any; - return { - ...rest, - vectorizeClassName: vectorizeCollectionName, - }; -}; - const collections = (connection: Connection, dbVersionSupport: DbVersionSupport) => { const listAll = () => new SchemaGetter(connection) @@ -137,43 +91,6 @@ const collections = (connection: Connection, dbVersionSupport: DbVersionSupport) moduleConfig[config.reranker.name] = config.reranker.config ? config.reranker.config : {}; } - const makeVectorsConfig = (configVectorizers: VectorizersConfigCreate) => { - let vectorizers: string[] = []; - const vectorsConfig: Record = {}; - const vectorizersConfig = Array.isArray(configVectorizers) - ? configVectorizers - : [ - { - ...configVectorizers, - name: 'default', - }, - ]; - vectorizersConfig.forEach((v) => { - if (v.vectorIndex.name === 'dynamic' && !supportsDynamicVectorIndex.supports) { - throw new WeaviateUnsupportedFeatureError(supportsDynamicVectorIndex.message); - } - const vectorConfig: any = { - vectorIndexConfig: parseVectorIndex(v.vectorIndex), - vectorIndexType: v.vectorIndex.name, - vectorizer: {}, - }; - const vectorizer = - v.vectorizer.name === 'text2vec-azure-openai' ? 'text2vec-openai' : v.vectorizer.name; - vectorizers = [...vectorizers, vectorizer]; - vectorConfig.vectorizer[vectorizer] = { - properties: v.properties, - ...parseVectorizerConfig(v.vectorizer.config), - }; - if (v.name === undefined) { - throw new WeaviateInvalidInputError( - 'vectorName is required for each vectorizer when specifying more than one vectorizer' - ); - } - vectorsConfig[v.name] = vectorConfig; - }); - return { vectorsConfig, vectorizers }; - }; - const makeLegacyVectorizer = ( configVectorizers: VectorConfigCreate, undefined, string, Vectorizer> ) => { @@ -221,7 +138,7 @@ const collections = (connection: Connection, dbVersionSupport: DbVersionSupport) let vectorizers: string[] = []; if (supportsNamedVectors.supports) { const { vectorsConfig, vectorizers: vecs } = config.vectorizers - ? makeVectorsConfig(config.vectorizers) + ? makeVectorsConfig(config.vectorizers, supportsDynamicVectorIndex) : { vectorsConfig: undefined, vectorizers: [] }; schema.vectorConfig = vectorsConfig; vectorizers = [...vecs]; From 44fde88347cc0d6b53a0e7a9d9fa4ce8cbec80f1 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 09:06:26 +0200 Subject: [PATCH 3/9] ci: add 1.31.0 to the text matrix Run the new integration test only if the server is > 1.31.0 --- .github/workflows/main.yaml | 1 + src/collections/config/integration.test.ts | 39 ++++++++++++---------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 69c5bdd5..335f89dd 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -14,6 +14,7 @@ env: WEAVIATE_128: 1.28.11 WEAVIATE_129: 1.29.1 WEAVIATE_130: 1.30.1 + WEAVIATE_131: 1.31.0 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/src/collections/config/integration.test.ts b/src/collections/config/integration.test.ts index 9cc08c6d..5dbafbe4 100644 --- a/src/collections/config/integration.test.ts +++ b/src/collections/config/integration.test.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { requireAtLeast } from '../../../test/version.js'; import { WeaviateUnsupportedFeatureError } from '../../errors.js'; import weaviate, { WeaviateClient, weaviateV2 } from '../../index.js'; import { @@ -386,25 +387,27 @@ describe('Testing of the collection.config namespace', () => { ]); }); - it('should be able to add a reference to a collection', async () => { - const collectionName = 'TestCollectionConfigAddVector' as const; - const collection = await client.collections.create({ - name: collectionName, - vectorizers: [weaviate.configure.vectorizer.none({ name: 'original' })], + requireAtLeast(1, 31, 0)('Mutable named vectors', () => { + it('should be able to add named vectors to a collection', async () => { + const collectionName = 'TestCollectionConfigAddVector' as const; + const collection = await client.collections.create({ + name: collectionName, + vectorizers: [weaviate.configure.vectorizer.none({ name: 'original' })], + }); + // Add a single named vector + await collection.config.addVector(weaviate.configure.vectorizer.none({ name: 'vector-a' })); + + // Add several named vectors + await collection.config.addVector([ + weaviate.configure.vectorizer.none({ name: 'vector-b' }), + weaviate.configure.vectorizer.none({ name: 'vector-c' }), + ]); + + const config = await collection.config.get(); + expect(config.vectorizers).toHaveProperty('vector-a'); + expect(config.vectorizers).toHaveProperty('vector-b'); + expect(config.vectorizers).toHaveProperty('vector-c'); }); - // Add a single named vector - await collection.config.addVector(weaviate.configure.vectorizer.none({ name: 'vector-a' })); - - // Add several named vectors - await collection.config.addVector([ - weaviate.configure.vectorizer.none({ name: 'vector-b' }), - weaviate.configure.vectorizer.none({ name: 'vector-c' }), - ]); - - const config = await collection.config.get(); - expect(config.vectorizers).toHaveProperty('vector-a'); - expect(config.vectorizers).toHaveProperty('vector-b'); - expect(config.vectorizers).toHaveProperty('vector-c'); }); it('should get the shards of a sharded collection', async () => { From e3fd2ccbd3215fccbaff31f705b5fa95a1fb4a57 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 09:27:40 +0200 Subject: [PATCH 4/9] fix: exclude existing vectors from request They are immutable in Weaviate and will cause an error --- src/collections/config/integration.test.ts | 8 ++++++++ src/schema/vectorAdder.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/collections/config/integration.test.ts b/src/collections/config/integration.test.ts index 5dbafbe4..79db4cad 100644 --- a/src/collections/config/integration.test.ts +++ b/src/collections/config/integration.test.ts @@ -403,10 +403,18 @@ describe('Testing of the collection.config namespace', () => { weaviate.configure.vectorizer.none({ name: 'vector-c' }), ]); + // Trying to update 'original' vector -- should be omitted from request. + await collection.config.addVector(weaviate.configure.vectorizer.none({ + name: 'original', + vectorIndexConfig: weaviate.configure.vectorIndex.flat(), + })); + const config = await collection.config.get(); expect(config.vectorizers).toHaveProperty('vector-a'); expect(config.vectorizers).toHaveProperty('vector-b'); expect(config.vectorizers).toHaveProperty('vector-c'); + + expect(config.vectorizers['original']).toHaveProperty('indexType', 'hnsw'); }); }); diff --git a/src/schema/vectorAdder.ts b/src/schema/vectorAdder.ts index cb3f164f..ccdd494e 100644 --- a/src/schema/vectorAdder.ts +++ b/src/schema/vectorAdder.ts @@ -46,6 +46,9 @@ export default class VectorAdder extends CommandBase { } for (const [key, value] of Object.entries(this.vectors)) { + if (schema.vectorConfig[key] !== undefined) { + continue; + } schema.vectorConfig![key] = { ...value }; } From 6c988cff53b701ef5b231fef6a4fb90321da1c14 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 09:27:52 +0200 Subject: [PATCH 5/9] chore: document addVector --- src/collections/config/index.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/collections/config/index.ts b/src/collections/config/index.ts index 70a21799..921ac2d1 100644 --- a/src/collections/config/index.ts +++ b/src/collections/config/index.ts @@ -40,7 +40,7 @@ const config = ( .withClassName(name) .withProperty(resolveProperty(property, [])) .do() - .then(() => {}), + .then(() => { }), addReference: ( reference: ReferenceSingleTargetConfigCreate | ReferenceMultiTargetConfigCreate ) => @@ -48,7 +48,7 @@ const config = ( .withClassName(name) .withProperty(resolveReference(reference)) .do() - .then(() => {}), + .then(() => { }), addVector: async (vectors: VectorizersConfigAdd) => { const supportsDynamicVectorIndex = await dbVersionSupport.supportsDynamicVectorIndex(); const { vectorsConfig } = makeVectorsConfig(vectors, supportsDynamicVectorIndex); @@ -72,7 +72,7 @@ const config = ( }) ); }, - updateShards: async function (status: 'READY' | 'READONLY', names?: string | string[]) { + updateShards: async function(status: 'READY' | 'READONLY', names?: string | string[]) { let shardNames: string[]; if (names === undefined) { shardNames = await this.getShards().then((shards) => shards.map((s) => s.name)); @@ -97,7 +97,7 @@ const config = ( ) ) .then((merged) => new ClassUpdater(connection).withClass(merged).do()) - .then(() => {}); + .then(() => { }); }, }; }; @@ -121,6 +121,16 @@ export interface Config { addReference: ( reference: ReferenceSingleTargetConfigCreate | ReferenceMultiTargetConfigCreate ) => Promise; + /** + * Add one or more named vectors to the collection in Weaviate. + * Named vectors can be added to collections with existing named vectors only. + * + * Existing named vectors are immutable in Weaviate. The client will not include + * any of those in the request. + * + * @param {VectorizersConfigAdd} vectors Vector configurations. + * @returns {Promise} A promise that resolves when the named vector has been created. + */ addVector: (vectors: VectorizersConfigAdd) => Promise; /** * Get the configuration for this collection from Weaviate. From ff15a4ef38633f9c610b59ffb163718f5846399c Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 09:29:39 +0200 Subject: [PATCH 6/9] chore: lint and format --- src/collections/config/index.ts | 26 +++++++++++----------- src/collections/config/integration.test.ts | 18 ++++++++++----- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/collections/config/index.ts b/src/collections/config/index.ts index 921ac2d1..ee982d3f 100644 --- a/src/collections/config/index.ts +++ b/src/collections/config/index.ts @@ -40,7 +40,7 @@ const config = ( .withClassName(name) .withProperty(resolveProperty(property, [])) .do() - .then(() => { }), + .then(() => {}), addReference: ( reference: ReferenceSingleTargetConfigCreate | ReferenceMultiTargetConfigCreate ) => @@ -48,7 +48,7 @@ const config = ( .withClassName(name) .withProperty(resolveReference(reference)) .do() - .then(() => { }), + .then(() => {}), addVector: async (vectors: VectorizersConfigAdd) => { const supportsDynamicVectorIndex = await dbVersionSupport.supportsDynamicVectorIndex(); const { vectorsConfig } = makeVectorsConfig(vectors, supportsDynamicVectorIndex); @@ -72,7 +72,7 @@ const config = ( }) ); }, - updateShards: async function(status: 'READY' | 'READONLY', names?: string | string[]) { + updateShards: async function (status: 'READY' | 'READONLY', names?: string | string[]) { let shardNames: string[]; if (names === undefined) { shardNames = await this.getShards().then((shards) => shards.map((s) => s.name)); @@ -97,7 +97,7 @@ const config = ( ) ) .then((merged) => new ClassUpdater(connection).withClass(merged).do()) - .then(() => { }); + .then(() => {}); }, }; }; @@ -122,15 +122,15 @@ export interface Config { reference: ReferenceSingleTargetConfigCreate | ReferenceMultiTargetConfigCreate ) => Promise; /** - * Add one or more named vectors to the collection in Weaviate. - * Named vectors can be added to collections with existing named vectors only. - * - * Existing named vectors are immutable in Weaviate. The client will not include - * any of those in the request. - * - * @param {VectorizersConfigAdd} vectors Vector configurations. - * @returns {Promise} A promise that resolves when the named vector has been created. - */ + * Add one or more named vectors to the collection in Weaviate. + * Named vectors can be added to collections with existing named vectors only. + * + * Existing named vectors are immutable in Weaviate. The client will not include + * any of those in the request. + * + * @param {VectorizersConfigAdd} vectors Vector configurations. + * @returns {Promise} A promise that resolves when the named vector has been created. + */ addVector: (vectors: VectorizersConfigAdd) => Promise; /** * Get the configuration for this collection from Weaviate. diff --git a/src/collections/config/integration.test.ts b/src/collections/config/integration.test.ts index 79db4cad..f122242c 100644 --- a/src/collections/config/integration.test.ts +++ b/src/collections/config/integration.test.ts @@ -387,7 +387,11 @@ describe('Testing of the collection.config namespace', () => { ]); }); - requireAtLeast(1, 31, 0)('Mutable named vectors', () => { + requireAtLeast( + 1, + 31, + 0 + )('Mutable named vectors', () => { it('should be able to add named vectors to a collection', async () => { const collectionName = 'TestCollectionConfigAddVector' as const; const collection = await client.collections.create({ @@ -404,17 +408,19 @@ describe('Testing of the collection.config namespace', () => { ]); // Trying to update 'original' vector -- should be omitted from request. - await collection.config.addVector(weaviate.configure.vectorizer.none({ - name: 'original', - vectorIndexConfig: weaviate.configure.vectorIndex.flat(), - })); + await collection.config.addVector( + weaviate.configure.vectorizer.none({ + name: 'original', + vectorIndexConfig: weaviate.configure.vectorIndex.flat(), + }) + ); const config = await collection.config.get(); expect(config.vectorizers).toHaveProperty('vector-a'); expect(config.vectorizers).toHaveProperty('vector-b'); expect(config.vectorizers).toHaveProperty('vector-c'); - expect(config.vectorizers['original']).toHaveProperty('indexType', 'hnsw'); + expect(config.vectorizers.original).toHaveProperty('indexType', 'hnsw'); }); }); From cb218772fafe5937763cc9943dca00f85546d4ae Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 10:43:37 +0200 Subject: [PATCH 7/9] test: check compatibility with default named vector --- src/collections/config/integration.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/collections/config/integration.test.ts b/src/collections/config/integration.test.ts index f122242c..a36f59f3 100644 --- a/src/collections/config/integration.test.ts +++ b/src/collections/config/integration.test.ts @@ -396,7 +396,7 @@ describe('Testing of the collection.config namespace', () => { const collectionName = 'TestCollectionConfigAddVector' as const; const collection = await client.collections.create({ name: collectionName, - vectorizers: [weaviate.configure.vectorizer.none({ name: 'original' })], + vectorizers: weaviate.configure.vectorizer.none(), }); // Add a single named vector await collection.config.addVector(weaviate.configure.vectorizer.none({ name: 'vector-a' })); @@ -407,10 +407,10 @@ describe('Testing of the collection.config namespace', () => { weaviate.configure.vectorizer.none({ name: 'vector-c' }), ]); - // Trying to update 'original' vector -- should be omitted from request. + // Trying to update 'default' vector -- should be omitted from request. await collection.config.addVector( weaviate.configure.vectorizer.none({ - name: 'original', + name: 'default', vectorIndexConfig: weaviate.configure.vectorIndex.flat(), }) ); @@ -420,7 +420,7 @@ describe('Testing of the collection.config namespace', () => { expect(config.vectorizers).toHaveProperty('vector-b'); expect(config.vectorizers).toHaveProperty('vector-c'); - expect(config.vectorizers.original).toHaveProperty('indexType', 'hnsw'); + expect(config.vectorizers['default']).toHaveProperty('indexType', 'hnsw'); }); }); From 0a5c43f29c3a3a4f0bd485b2842da92ee35d35a6 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 11:16:28 +0200 Subject: [PATCH 8/9] chore: use dot-notation to access a named vector --- src/collections/config/integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collections/config/integration.test.ts b/src/collections/config/integration.test.ts index a36f59f3..9b363d06 100644 --- a/src/collections/config/integration.test.ts +++ b/src/collections/config/integration.test.ts @@ -420,7 +420,7 @@ describe('Testing of the collection.config namespace', () => { expect(config.vectorizers).toHaveProperty('vector-b'); expect(config.vectorizers).toHaveProperty('vector-c'); - expect(config.vectorizers['default']).toHaveProperty('indexType', 'hnsw'); + expect(config.vectorizers.default).toHaveProperty('indexType', 'hnsw'); }); }); From 40d78416c798e0f32d007d3eaa8cf48d5fab0bbc Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 2 Jun 2025 13:28:22 +0200 Subject: [PATCH 9/9] feat: allow creating single named vector --- src/collections/configure/types/vectorizer.ts | 2 +- src/collections/index.ts | 2 +- src/collections/integration.test.ts | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/collections/configure/types/vectorizer.ts b/src/collections/configure/types/vectorizer.ts index 4de07cbf..14f32da7 100644 --- a/src/collections/configure/types/vectorizer.ts +++ b/src/collections/configure/types/vectorizer.ts @@ -55,7 +55,7 @@ export type VectorConfigUpdate = - | VectorConfigCreate, undefined, VectorIndexType, Vectorizer> + | VectorConfigCreate, string | undefined, VectorIndexType, Vectorizer> | VectorConfigCreate, string, VectorIndexType, Vectorizer>[]; export type VectorizersConfigAdd = diff --git a/src/collections/index.ts b/src/collections/index.ts index 9eb7b52e..cd5707a7 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -147,7 +147,7 @@ const collections = (connection: Connection, dbVersionSupport: DbVersionSupport) throw new WeaviateUnsupportedFeatureError(supportsNamedVectors.message); } const configs = config.vectorizers - ? makeLegacyVectorizer(config.vectorizers) + ? makeLegacyVectorizer({ ...config.vectorizers, name: undefined }) : { vectorizer: undefined, moduleConfig: undefined, diff --git a/src/collections/integration.test.ts b/src/collections/integration.test.ts index f143843d..df424745 100644 --- a/src/collections/integration.test.ts +++ b/src/collections/integration.test.ts @@ -174,6 +174,20 @@ describe('Testing of the collections.create method', () => { expect(response.vectorizers.default.vectorizer.name).toEqual('text2vec-contextionary'); }); + it('should be able to create a collection with 1 custom named vector', async () => { + const collectionName = 'TestCollectionSingleCustomNamedVector'; + const response = await contextionary.collections + .create({ + name: collectionName, + vectorizers: weaviate.configure.vectorizer.none({ name: 'custom' }), + }) + .then(() => contextionary.collections.use(collectionName).config.get()); + expect(response.name).toEqual(collectionName); + expect(response.properties?.length).toEqual(0); + expect(response.vectorizers.custom.indexConfig).toBeDefined(); + expect(response.vectorizers.custom.indexType).toEqual('hnsw'); + }); + it('should be able to create a simple collection without a generic using a schema var', async () => { const collectionName = 'TestCollectionSimpleNonGenericVar'; const schema = {