Skip to content

Commit aef7fa7

Browse files
authored
Merge pull request #209 from weaviate/1.27/multi-vector-search
Support multi vector search while providing BC for deprecated protos
2 parents 5dec0a9 + 1f62379 commit aef7fa7

File tree

17 files changed

+5906
-788
lines changed

17 files changed

+5906
-788
lines changed

.github/workflows/main.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- uses: actions/checkout@v3
2020
- uses: actions/setup-node@v3
2121
with:
22-
node-version: '18.x'
22+
node-version: '22.x'
2323
- name: "Run checks"
2424
run: |
2525
npm ci
@@ -81,7 +81,7 @@ jobs:
8181
# Setup .npmrc file to publish to npm
8282
- uses: actions/setup-node@v3
8383
with:
84-
node-version: '18.x'
84+
node-version: '22.x'
8585
registry-url: 'https://registry.npmjs.org'
8686
- run: npm ci
8787
- run: npm run build

ci/run_dependencies.sh

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,24 @@ function wait(){
2121

2222
echo "Waiting for $1"
2323
while true; do
24-
if curl -s $1 > /dev/null; then
24+
# first check if weaviate already responds
25+
if ! curl -s $1 > /dev/null; then
26+
continue
27+
fi
28+
29+
# endpoint available, check if it is ready
30+
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$1/v1/.well-known/ready")
31+
32+
if [ "$HTTP_STATUS" -eq 200 ]; then
2533
break
2634
else
27-
if [ $? -eq 7 ]; then
28-
echo "Weaviate is not up yet. (waited for ${ALREADY_WAITING}s)"
29-
if [ $ALREADY_WAITING -gt $MAX_WAIT_SECONDS ]; then
30-
echo "Weaviate did not start up in $MAX_WAIT_SECONDS."
31-
exit 1
32-
else
33-
sleep 2
34-
let ALREADY_WAITING=$ALREADY_WAITING+2
35-
fi
35+
echo "Weaviate is not up yet. (waited for ${ALREADY_WAITING}s)"
36+
if [ $ALREADY_WAITING -gt $MAX_WAIT_SECONDS ]; then
37+
echo "Weaviate did not start up in $MAX_WAIT_SECONDS."
38+
exit 1
39+
else
40+
sleep 2
41+
let ALREADY_WAITING=$ALREADY_WAITING+2
3642
fi
3743
fi
3844
done

src/collections/generate/index.ts

Lines changed: 69 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import Connection from '../../connection/grpc.js';
33
import { ConsistencyLevel } from '../../data/index.js';
44
import { DbVersionSupport } from '../../utils/dbVersion.js';
55

6-
import { WeaviateInvalidInputError, WeaviateUnsupportedFeatureError } from '../../errors.js';
6+
import { WeaviateInvalidInputError } from '../../errors.js';
77
import { toBase64FromMedia } from '../../index.js';
88
import { SearchReply } from '../../proto/v1/search_get.js';
99
import { Deserialize } from '../deserialize/index.js';
10+
import { Check } from '../query/check.js';
1011
import {
1112
BaseBm25Options,
1213
BaseHybridOptions,
@@ -34,24 +35,10 @@ import {
3435
import { Generate } from './types.js';
3536

3637
class GenerateManager<T> implements Generate<T> {
37-
private connection: Connection;
38-
private name: string;
39-
private dbVersionSupport: DbVersionSupport;
40-
private consistencyLevel?: ConsistencyLevel;
41-
private tenant?: string;
38+
private check: Check<T>;
4239

43-
private constructor(
44-
connection: Connection,
45-
name: string,
46-
dbVersionSupport: DbVersionSupport,
47-
consistencyLevel?: ConsistencyLevel,
48-
tenant?: string
49-
) {
50-
this.connection = connection;
51-
this.name = name;
52-
this.dbVersionSupport = dbVersionSupport;
53-
this.consistencyLevel = consistencyLevel;
54-
this.tenant = tenant;
40+
private constructor(check: Check<T>) {
41+
this.check = check;
5542
}
5643

5744
public static use<T>(
@@ -61,78 +48,29 @@ class GenerateManager<T> implements Generate<T> {
6148
consistencyLevel?: ConsistencyLevel,
6249
tenant?: string
6350
): GenerateManager<T> {
64-
return new GenerateManager<T>(connection, name, dbVersionSupport, consistencyLevel, tenant);
51+
return new GenerateManager<T>(new Check<T>(connection, name, dbVersionSupport, consistencyLevel, tenant));
6552
}
6653

67-
private checkSupportForNamedVectors = async (opts?: BaseNearOptions<T>) => {
68-
if (!Serialize.isNamedVectors(opts)) return;
69-
const check = await this.dbVersionSupport.supportsNamedVectors();
70-
if (!check.supports) throw new WeaviateUnsupportedFeatureError(check.message);
71-
};
72-
73-
private checkSupportForBm25AndHybridGroupByQueries = async (query: 'Bm25' | 'Hybrid', opts?: any) => {
74-
if (!Serialize.isGroupBy(opts)) return;
75-
const check = await this.dbVersionSupport.supportsBm25AndHybridGroupByQueries();
76-
if (!check.supports) throw new WeaviateUnsupportedFeatureError(check.message(query));
77-
};
78-
79-
private checkSupportForHybridNearTextAndNearVectorSubSearches = async (opts?: HybridOptions<T>) => {
80-
if (opts?.vector === undefined || Array.isArray(opts.vector)) return;
81-
const check = await this.dbVersionSupport.supportsHybridNearTextAndNearVectorSubsearchQueries();
82-
if (!check.supports) throw new WeaviateUnsupportedFeatureError(check.message);
83-
};
84-
85-
private checkSupportForMultiTargetVectorSearch = async (opts?: BaseNearOptions<T>) => {
86-
if (!Serialize.isMultiTargetVector(opts)) return false;
87-
const check = await this.dbVersionSupport.supportsMultiTargetVectorSearch();
88-
if (!check.supports) throw new WeaviateUnsupportedFeatureError(check.message);
89-
return check.supports;
90-
};
91-
92-
private nearSearch = async (opts?: BaseNearOptions<T>) => {
93-
const [_, supportsTargets] = await Promise.all([
94-
this.checkSupportForNamedVectors(opts),
95-
this.checkSupportForMultiTargetVectorSearch(opts),
96-
]);
97-
return {
98-
search: await this.connection.search(this.name, this.consistencyLevel, this.tenant),
99-
supportsTargets,
100-
};
101-
};
102-
103-
private hybridSearch = async (opts?: BaseHybridOptions<T>) => {
104-
const [supportsTargets] = await Promise.all([
105-
this.checkSupportForMultiTargetVectorSearch(opts),
106-
this.checkSupportForNamedVectors(opts),
107-
this.checkSupportForBm25AndHybridGroupByQueries('Hybrid', opts),
108-
this.checkSupportForHybridNearTextAndNearVectorSubSearches(opts),
109-
]);
110-
return {
111-
search: await this.connection.search(this.name, this.consistencyLevel, this.tenant),
112-
supportsTargets,
113-
};
114-
};
115-
11654
private async parseReply(reply: SearchReply) {
117-
const deserialize = await Deserialize.use(this.dbVersionSupport);
55+
const deserialize = await Deserialize.use(this.check.dbVersionSupport);
11856
return deserialize.generate<T>(reply);
11957
}
12058

12159
private async parseGroupByReply(
12260
opts: SearchOptions<T> | GroupByOptions<T> | undefined,
12361
reply: SearchReply
12462
) {
125-
const deserialize = await Deserialize.use(this.dbVersionSupport);
63+
const deserialize = await Deserialize.use(this.check.dbVersionSupport);
12664
return Serialize.isGroupBy(opts) ? deserialize.generateGroupBy<T>(reply) : deserialize.generate<T>(reply);
12765
}
12866

12967
public fetchObjects(
13068
generate: GenerateOptions<T>,
13169
opts?: FetchObjectsOptions<T>
13270
): Promise<GenerativeReturn<T>> {
133-
return this.checkSupportForNamedVectors(opts)
134-
.then(() => this.connection.search(this.name, this.consistencyLevel, this.tenant))
135-
.then((search) =>
71+
return this.check
72+
.fetchObjects(opts)
73+
.then(({ search }) =>
13674
search.withFetch({
13775
...Serialize.fetchObjects(opts),
13876
generative: Serialize.generative(generate),
@@ -152,12 +90,9 @@ class GenerateManager<T> implements Generate<T> {
15290
opts: GroupByBm25Options<T>
15391
): Promise<GenerativeGroupByReturn<T>>;
15492
public bm25(query: string, generate: GenerateOptions<T>, opts?: Bm25Options<T>): GenerateReturn<T> {
155-
return Promise.all([
156-
this.checkSupportForNamedVectors(opts),
157-
this.checkSupportForBm25AndHybridGroupByQueries('Bm25', opts),
158-
])
159-
.then(() => this.connection.search(this.name, this.consistencyLevel, this.tenant))
160-
.then((search) =>
93+
return this.check
94+
.bm25(opts)
95+
.then(({ search }) =>
16196
search.withBm25({
16297
...Serialize.bm25({ query, ...opts }),
16398
generative: Serialize.generative(generate),
@@ -180,10 +115,17 @@ class GenerateManager<T> implements Generate<T> {
180115
opts: GroupByHybridOptions<T>
181116
): Promise<GenerativeGroupByReturn<T>>;
182117
public hybrid(query: string, generate: GenerateOptions<T>, opts?: HybridOptions<T>): GenerateReturn<T> {
183-
return this.hybridSearch(opts)
184-
.then(({ search, supportsTargets }) =>
118+
return this.check
119+
.hybridSearch(opts)
120+
.then(({ search, supportsTargets, supportsVectorsForTargets, supportsWeightsForTargets }) =>
185121
search.withHybrid({
186-
...Serialize.hybrid({ query, supportsTargets, ...opts }),
122+
...Serialize.hybrid({
123+
query,
124+
supportsTargets,
125+
supportsVectorsForTargets,
126+
supportsWeightsForTargets,
127+
...opts,
128+
}),
187129
generative: Serialize.generative(generate),
188130
groupBy: Serialize.isGroupBy<GroupByHybridOptions<T>>(opts)
189131
? Serialize.groupBy(opts.groupBy)
@@ -208,11 +150,17 @@ class GenerateManager<T> implements Generate<T> {
208150
generate: GenerateOptions<T>,
209151
opts?: NearOptions<T>
210152
): GenerateReturn<T> {
211-
return this.nearSearch(opts)
212-
.then(({ search, supportsTargets }) =>
153+
return this.check
154+
.nearSearch(opts)
155+
.then(({ search, supportsTargets, supportsWeightsForTargets }) =>
213156
toBase64FromMedia(image).then((image) =>
214157
search.withNearImage({
215-
...Serialize.nearImage({ image, supportsTargets, ...(opts ? opts : {}) }),
158+
...Serialize.nearImage({
159+
image,
160+
supportsTargets,
161+
supportsWeightsForTargets,
162+
...(opts ? opts : {}),
163+
}),
216164
generative: Serialize.generative(generate),
217165
groupBy: Serialize.isGroupBy<GroupByNearOptions<T>>(opts)
218166
? Serialize.groupBy(opts.groupBy)
@@ -234,10 +182,16 @@ class GenerateManager<T> implements Generate<T> {
234182
opts: GroupByNearOptions<T>
235183
): Promise<GenerativeGroupByReturn<T>>;
236184
public nearObject(id: string, generate: GenerateOptions<T>, opts?: NearOptions<T>): GenerateReturn<T> {
237-
return this.nearSearch(opts)
238-
.then(({ search, supportsTargets }) =>
185+
return this.check
186+
.nearSearch(opts)
187+
.then(({ search, supportsTargets, supportsWeightsForTargets }) =>
239188
search.withNearObject({
240-
...Serialize.nearObject({ id, supportsTargets, ...(opts ? opts : {}) }),
189+
...Serialize.nearObject({
190+
id,
191+
supportsTargets,
192+
supportsWeightsForTargets,
193+
...(opts ? opts : {}),
194+
}),
241195
generative: Serialize.generative(generate),
242196
groupBy: Serialize.isGroupBy<GroupByNearOptions<T>>(opts)
243197
? Serialize.groupBy(opts.groupBy)
@@ -262,10 +216,16 @@ class GenerateManager<T> implements Generate<T> {
262216
generate: GenerateOptions<T>,
263217
opts?: NearOptions<T>
264218
): GenerateReturn<T> {
265-
return this.nearSearch(opts)
266-
.then(({ search, supportsTargets }) =>
219+
return this.check
220+
.nearSearch(opts)
221+
.then(({ search, supportsTargets, supportsWeightsForTargets }) =>
267222
search.withNearText({
268-
...Serialize.nearText({ query, supportsTargets, ...(opts ? opts : {}) }),
223+
...Serialize.nearText({
224+
query,
225+
supportsTargets,
226+
supportsWeightsForTargets,
227+
...(opts ? opts : {}),
228+
}),
269229
generative: Serialize.generative(generate),
270230
groupBy: Serialize.isGroupBy<GroupByNearOptions<T>>(opts)
271231
? Serialize.groupBy(opts.groupBy)
@@ -290,10 +250,17 @@ class GenerateManager<T> implements Generate<T> {
290250
generate: GenerateOptions<T>,
291251
opts?: NearOptions<T>
292252
): GenerateReturn<T> {
293-
return this.nearSearch(opts)
294-
.then(({ search, supportsTargets }) =>
253+
return this.check
254+
.nearVector(vector, opts)
255+
.then(({ search, supportsTargets, supportsVectorsForTargets, supportsWeightsForTargets }) =>
295256
search.withNearVector({
296-
...Serialize.nearVector({ vector, supportsTargets, ...(opts ? opts : {}) }),
257+
...Serialize.nearVector({
258+
vector,
259+
supportsTargets,
260+
supportsVectorsForTargets,
261+
supportsWeightsForTargets,
262+
...(opts ? opts : {}),
263+
}),
297264
generative: Serialize.generative(generate),
298265
groupBy: Serialize.isGroupBy<GroupByNearOptions<T>>(opts)
299266
? Serialize.groupBy(opts.groupBy)
@@ -321,10 +288,15 @@ class GenerateManager<T> implements Generate<T> {
321288
generate: GenerateOptions<T>,
322289
opts?: NearOptions<T>
323290
): GenerateReturn<T> {
324-
return this.nearSearch(opts)
325-
.then(({ search, supportsTargets }) => {
291+
return this.check
292+
.nearSearch(opts)
293+
.then(({ search, supportsTargets, supportsWeightsForTargets }) => {
326294
let reply: Promise<SearchReply>;
327-
const args = { supportsTargets, ...(opts ? opts : {}) };
295+
const args = {
296+
supportsTargets,
297+
supportsWeightsForTargets,
298+
...(opts ? opts : {}),
299+
};
328300
const generative = Serialize.generative(generate);
329301
const groupBy = Serialize.isGroupBy<GroupByNearOptions<T>>(opts)
330302
? Serialize.groupBy(opts.groupBy)

src/collections/generate/integration.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,11 @@ maybe('Testing of the collection.generate methods with a multi vector collection
379379
it('should generate with a near vector search on multi vectors', async () => {
380380
const query = () =>
381381
collection.generate.nearVector(
382-
[titleVector, title2Vector],
382+
{ title: titleVector, title2: title2Vector },
383383
{
384384
groupedTask: 'What is the value of title here? {title}',
385385
groupedProperties: ['title'],
386386
singlePrompt: 'Write a haiku about ducks for {title}',
387-
},
388-
{
389-
targetVector: ['title', 'title2'],
390387
}
391388
);
392389
if (await client.getWeaviateVersion().then((ver) => ver.isLowerThan(1, 26, 0))) {

0 commit comments

Comments
 (0)