Skip to content

Commit 7c1ab25

Browse files
committed
Add support for hybrid subsearches
1 parent 6a9151e commit 7c1ab25

File tree

4 files changed

+254
-41
lines changed

4 files changed

+254
-41
lines changed

src/graphql/getter.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,34 @@ describe('hybrid valid searchers', () => {
14131413

14141414
expect(mockClient.query).toHaveBeenCalledWith(expectedQuery);
14151415
});
1416+
1417+
test('query and subsearches', () => {
1418+
const subQuery = `searches:[{vector:[1,2,3],certainty:0.8,targetVectors:["employer"]},{concepts:["accountant"],distance:0.3,moveTo:{concepts:["foo"],objects:[{id:"uuid"}],force:0.8}}]`;
1419+
const expectedQuery = `{Get{Person(hybrid:{query:"accountant",${subQuery}}){name}}}`;
1420+
1421+
new Getter(mockClient)
1422+
.withClassName('Person')
1423+
.withFields('name')
1424+
.withHybrid({
1425+
query: 'accountant',
1426+
searches: [
1427+
{ certainty: 0.8, targetVectors: ['employer'], vector: [1, 2, 3] },
1428+
{
1429+
concepts: ['accountant'],
1430+
distance: 0.3,
1431+
moveTo: {
1432+
concepts: ['foo'],
1433+
objects: [{ id: 'uuid' }],
1434+
force: 0.8,
1435+
},
1436+
},
1437+
],
1438+
})
1439+
.do();
1440+
1441+
console.log(mockClient.query.mock.calls);
1442+
expect(mockClient.query).toHaveBeenCalledWith(expectedQuery);
1443+
});
14161444
});
14171445

14181446
describe('generative search', () => {

src/graphql/hybrid.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,126 @@
1+
import { Move, parseMove } from './nearText';
2+
13
export interface HybridArgs {
24
alpha?: number;
35
query: string;
46
vector?: number[];
57
properties?: string[];
68
targetVectors?: string[];
79
fusionType?: FusionType;
10+
searches?: (NearTextSubSearch | NearVectorSubSearch)[];
11+
}
12+
13+
export interface NearTextSubSearch {
14+
concepts: string[];
15+
certainty?: number;
16+
distance?: number;
17+
moveAwayFrom?: Move;
18+
moveTo?: Move;
19+
}
20+
21+
export interface NearVectorSubSearch {
22+
vector: number[];
23+
certainty?: number;
24+
distance?: number;
25+
targetVectors?: string[];
826
}
927

1028
export enum FusionType {
1129
rankedFusion = 'rankedFusion',
1230
relativeScoreFusion = 'relativeScoreFusion',
1331
}
1432

33+
class GraphQLHybridSubSearch {
34+
constructor() {
35+
if (new.target === GraphQLHybridSubSearch) {
36+
throw new Error('Cannot instantiate abstract class');
37+
}
38+
}
39+
40+
static fromArgs(args: NearTextSubSearch | NearVectorSubSearch): GraphQLHybridSubSearch {
41+
if ('concepts' in args) {
42+
return new GraphQLHybridSubSearchNearText(args);
43+
} else {
44+
return new GraphQLHybridSubSearchNearVector(args);
45+
}
46+
}
47+
48+
toString(): string {
49+
throw new Error('Method not implemented.');
50+
}
51+
}
52+
53+
class GraphQLHybridSubSearchNearText extends GraphQLHybridSubSearch {
54+
private concepts: string[];
55+
private certainty?: number;
56+
private distance?: number;
57+
private moveAwayFrom?: Move;
58+
private moveTo?: Move;
59+
60+
constructor(args: NearTextSubSearch) {
61+
super();
62+
this.concepts = args.concepts;
63+
this.certainty = args.certainty;
64+
this.distance = args.distance;
65+
this.moveAwayFrom = args.moveAwayFrom;
66+
this.moveTo = args.moveTo;
67+
}
68+
69+
toString(): string {
70+
let args = [`concepts:${JSON.stringify(this.concepts)}`];
71+
if (this.certainty) {
72+
args = [...args, `certainty:${this.certainty}`];
73+
}
74+
if (this.distance) {
75+
args = [...args, `distance:${this.distance}`];
76+
}
77+
if (this.moveTo) {
78+
args = [...args, parseMove('moveTo', this.moveTo)];
79+
}
80+
if (this.moveAwayFrom) {
81+
args = [...args, parseMove('moveAwayFrom', this.moveAwayFrom)];
82+
}
83+
return `{${args.join(',')}}`;
84+
}
85+
}
86+
87+
class GraphQLHybridSubSearchNearVector extends GraphQLHybridSubSearch {
88+
private vector: number[];
89+
private certainty?: number;
90+
private distance?: number;
91+
private targetVectors?: string[];
92+
93+
constructor(args: NearVectorSubSearch) {
94+
super();
95+
this.vector = args.vector;
96+
this.certainty = args.certainty;
97+
this.distance = args.distance;
98+
this.targetVectors = args.targetVectors;
99+
}
100+
101+
toString(): string {
102+
let args = [`vector:${JSON.stringify(this.vector)}`];
103+
if (this.certainty) {
104+
args = [...args, `certainty:${this.certainty}`];
105+
}
106+
if (this.distance) {
107+
args = [...args, `distance:${this.distance}`];
108+
}
109+
if (this.targetVectors && this.targetVectors.length > 0) {
110+
args = [...args, `targetVectors:${JSON.stringify(this.targetVectors)}`];
111+
}
112+
return `{${args.join(',')}}`;
113+
}
114+
}
115+
15116
export default class GraphQLHybrid {
16117
private alpha?: number;
17118
private query: string;
18119
private vector?: number[];
19120
private properties?: string[];
20121
private targetVectors?: string[];
21122
private fusionType?: FusionType;
123+
private searches?: GraphQLHybridSubSearch[];
22124

23125
constructor(args: HybridArgs) {
24126
this.alpha = args.alpha;
@@ -27,6 +129,7 @@ export default class GraphQLHybrid {
27129
this.properties = args.properties;
28130
this.targetVectors = args.targetVectors;
29131
this.fusionType = args.fusionType;
132+
this.searches = args.searches?.map((search) => GraphQLHybridSubSearch.fromArgs(search));
30133
}
31134

32135
toString() {
@@ -52,6 +155,10 @@ export default class GraphQLHybrid {
52155
args = [...args, `fusionType:${this.fusionType}`];
53156
}
54157

158+
if (this.searches !== undefined) {
159+
args = [...args, `searches:[${this.searches.map((search) => search.toString()).join(',')}]`];
160+
}
161+
55162
return `{${args.join(',')}}`;
56163
}
57164
}

src/graphql/journey.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,93 @@ describe('the graphql journey', () => {
464464
});
465465
});
466466

467+
test('graphql get hybrid with query and nearText subsearch', () => {
468+
return client.graphql
469+
.get()
470+
.withClassName('Article')
471+
.withHybrid({
472+
query: '',
473+
searches: [
474+
{
475+
concepts: ['Article'],
476+
certainty: 0.7,
477+
},
478+
],
479+
})
480+
.withFields('_additional { id }')
481+
.do()
482+
.then((res: any) => {
483+
expect(res.data.Get.Article.length).toBe(3);
484+
})
485+
.catch((e: any) => {
486+
throw new Error('it should not have errord' + e);
487+
});
488+
});
489+
490+
test('graphql get hybrid with query and nearVector subsearch', () => {
491+
const searchVec = [
492+
-0.15047126, 0.061322376, -0.17812507, 0.12811552, 0.36847013, -0.50840724, -0.10406531, 0.11413283,
493+
0.2997712, 0.7039331, 0.22155242, 0.1413957, 0.025396502, 0.14802167, 0.26640236, 0.15965445,
494+
-0.45570126, -0.5215438, 0.14628491, 0.10946681, 0.0040095793, 0.017442623, -0.1988451, -0.05362646,
495+
0.104278944, -0.2506941, 0.2667653, 0.36438593, -0.44370207, 0.07204353, 0.077371456, 0.14557181,
496+
0.6026817, 0.45073593, 0.09438019, 0.03936342, -0.20441438, 0.12333719, -0.20247602, 0.5078446,
497+
-0.06079732, -0.02166342, 0.02165861, -0.11712191, 0.0493167, -0.012123002, 0.26458082, -0.10784768,
498+
-0.26852348, 0.049759883, -0.39999008, -0.08977922, 0.003169497, -0.36184034, -0.069065355, 0.18940343,
499+
0.5684866, -0.24626277, -0.2326087, 0.090373255, 0.33161184, -1.0541122, -0.039116446, -0.17496277,
500+
-0.16834813, -0.0765323, -0.16189013, -0.062876746, -0.19826415, 0.07437007, -0.018362755, 0.23634757,
501+
-0.19062655, -0.26524994, 0.33691254, -0.1926698, 0.018848037, 0.1735524, 0.34301907, -0.014238952,
502+
-0.07596742, -0.61302894, -0.044652265, 0.1545376, 0.67256856, 0.08630557, 0.50236076, 0.23438522,
503+
0.27686095, 0.13633616, -0.27525797, 0.04282576, 0.18319897, -0.008353968, -0.27330264, 0.12624736,
504+
-0.17051372, -0.35854533, -0.008455927, 0.154786, -0.20306401, -0.09021733, 0.80594194, 0.036562894,
505+
-0.48894945, -0.27981675, -0.5001396, -0.3581464, -0.057082724, -0.0051904973, -0.3209166, 0.057098284,
506+
0.111587055, -0.09097725, -0.213181, -0.5038173, -0.024070809, -0.05350453, 0.13345918, -0.42136985,
507+
0.24050911, -0.2556207, 0.03156968, 0.4381214, 0.053237516, -0.20783865, 1.885739, 0.28429136,
508+
-0.12231187, -0.30934808, 0.032250155, -0.32959512, 0.08670603, -0.60112613, -0.43010503, 0.70870006,
509+
0.3548015, -0.010406012, 0.036294986, 0.0030629474, -0.017579105, 0.28948352, -0.48063236, -0.39739868,
510+
0.17860937, 0.5099417, -0.24304488, -0.12671146, -0.018249692, -0.32057074, -0.08146134, 0.3572229,
511+
-0.47601065, 0.35100546, -0.19663939, 0.34194613, -0.04653828, 0.47278664, -0.8723091, -0.19756387,
512+
-0.5890681, 0.16688067, -0.23709822, -0.26478595, -0.18792373, 0.2204168, 0.030987943, 0.15885714,
513+
-0.38817936, -0.4194334, -0.3287098, 0.15394142, -0.09496768, 0.6561987, -0.39340565, -0.5479265,
514+
-0.22363484, -0.1193662, 0.2014849, 0.31138006, -0.45485613, -0.9879565, 0.3708223, 0.17318928,
515+
0.21229307, 0.042776756, -0.077399045, 0.42621315, -0.09917796, 0.34220153, 0.06380378, 0.14129028,
516+
-0.14563583, -0.07081333, 0.026335392, 0.10566285, -0.28074324, -0.059861198, -0.24855351, 0.13623764,
517+
-0.8228192, -0.15095113, 0.16250934, 0.031107651, -0.1504525, 0.20840737, 0.12919411, -0.0926323,
518+
0.30937102, 0.16636328, -0.36754072, 0.035581365, -0.2799259, 0.1446048, -0.11680267, 0.13226685,
519+
0.175023, -0.18840964, 0.27609056, -0.09350581, 0.08284562, 0.45897093, 0.13188471, -0.07115303,
520+
0.18009436, 0.16689545, -0.6991295, 0.26496106, -0.29619592, -0.19242188, -0.6362671, -0.16330126,
521+
0.2474778, 0.37738156, -0.12921557, -0.07843309, 0.28509396, 0.5658691, 0.16096894, 0.095068075,
522+
0.02419672, -0.30691084, 0.21180221, 0.21670066, 0.0027263877, 0.30853105, -0.16187873, 0.20786561,
523+
0.22136153, -0.008828387, -0.011165021, 0.60076475, 0.0089871045, 0.6179727, -0.38049766, -0.08179336,
524+
-0.15306218, -0.13186441, -0.5360041, -0.06123339, -0.06399122, 0.21292226, -0.18383273, -0.21540102,
525+
0.28566808, -0.29953584, -0.36946672, 0.03341637, -0.08435299, -0.5381947, -0.28651953, 0.08704594,
526+
-0.25493965, 0.0019178925, -0.7242109, 0.3578676, -0.55617595, -0.01930952, 0.32922924, 0.14903364,
527+
0.21613406, -0.11927183, 0.15165499, -0.10101261, 0.2499076, -0.18526322, -0.057230365, 0.10008554,
528+
0.16178907, 0.39356324, -0.03106238, 0.09375929, 0.17185533, 0.10400415, -0.36850816, 0.18424486,
529+
-0.081376314, 0.23645392, 0.05198973, 0.09471436,
530+
];
531+
532+
return client.graphql
533+
.get()
534+
.withClassName('Article')
535+
.withHybrid({
536+
query: '',
537+
searches: [
538+
{
539+
vector: searchVec,
540+
distance: 0.3,
541+
},
542+
],
543+
})
544+
.withFields('_additional { id }')
545+
.do()
546+
.then((res: any) => {
547+
expect(res.data.Get.Article.length).toBe(3);
548+
})
549+
.catch((e: any) => {
550+
throw new Error('it should not have errord' + e);
551+
});
552+
});
553+
467554
test('graphql get with nearText (with certainty)', () => {
468555
return client.graphql
469556
.get()

src/graphql/nearText.ts

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -56,34 +56,11 @@ export default class GraphQLNearText {
5656
}
5757

5858
if (this.moveTo) {
59-
let moveToArgs: string[] = [];
60-
if (this.moveTo.concepts) {
61-
moveToArgs = [...moveToArgs, `concepts:${JSON.stringify(this.moveTo.concepts)}`];
62-
}
63-
if (this.moveTo.objects) {
64-
moveToArgs = [...moveToArgs, `objects:${this.parseMoveObjects('moveTo', this.moveTo.objects)}`];
65-
}
66-
if (this.moveTo.force) {
67-
moveToArgs = [...moveToArgs, `force:${this.moveTo.force}`];
68-
}
69-
args = [...args, `moveTo:{${moveToArgs.join(',')}}`];
59+
args = [...args, parseMove('moveTo', this.moveTo)];
7060
}
7161

7262
if (this.moveAwayFrom) {
73-
let moveAwayFromArgs: string[] = [];
74-
if (this.moveAwayFrom.concepts) {
75-
moveAwayFromArgs = [...moveAwayFromArgs, `concepts:${JSON.stringify(this.moveAwayFrom.concepts)}`];
76-
}
77-
if (this.moveAwayFrom.objects) {
78-
moveAwayFromArgs = [
79-
...moveAwayFromArgs,
80-
`objects:${this.parseMoveObjects('moveAwayFrom', this.moveAwayFrom.objects)}`,
81-
];
82-
}
83-
if (this.moveAwayFrom.force) {
84-
moveAwayFromArgs = [...moveAwayFromArgs, `force:${this.moveAwayFrom.force}`];
85-
}
86-
args = [...args, `moveAwayFrom:{${moveAwayFromArgs.join(',')}}`];
63+
args = [...args, parseMove('moveAwayFrom', this.moveAwayFrom)];
8764
}
8865

8966
if (this.autocorrect !== undefined) {
@@ -112,24 +89,38 @@ export default class GraphQLNearText {
11289
}
11390
}
11491
}
92+
}
11593

116-
parseMoveObjects(move: MoveType, objects: MoveObject[]): string {
117-
const moveObjects: string[] = [];
118-
for (const i in objects) {
119-
if (!objects[i].id && !objects[i].beacon) {
120-
throw new Error(`nearText: ${move}.objects[${i}].id or ${move}.objects[${i}].beacon must be present`);
121-
}
122-
const objs = [];
123-
if (objects[i].id) {
124-
objs.push(`id:"${objects[i].id}"`);
125-
}
126-
if (objects[i].beacon) {
127-
objs.push(`beacon:"${objects[i].beacon}"`);
128-
}
129-
moveObjects.push(`{${objs.join(',')}}`);
94+
type MoveType = 'moveTo' | 'moveAwayFrom';
95+
96+
export function parseMoveObjects(move: MoveType, objects: MoveObject[]): string {
97+
const moveObjects: string[] = [];
98+
for (const i in objects) {
99+
if (!objects[i].id && !objects[i].beacon) {
100+
throw new Error(`nearText: ${move}.objects[${i}].id or ${move}.objects[${i}].beacon must be present`);
101+
}
102+
const objs = [];
103+
if (objects[i].id) {
104+
objs.push(`id:"${objects[i].id}"`);
130105
}
131-
return `[${moveObjects.join(',')}]`;
106+
if (objects[i].beacon) {
107+
objs.push(`beacon:"${objects[i].beacon}"`);
108+
}
109+
moveObjects.push(`{${objs.join(',')}}`);
132110
}
111+
return `[${moveObjects.join(',')}]`;
133112
}
134113

135-
type MoveType = 'moveTo' | 'moveAwayFrom';
114+
export function parseMove(move: MoveType, args: Move): string {
115+
let moveArgs: string[] = [];
116+
if (args.concepts) {
117+
moveArgs = [...moveArgs, `concepts:${JSON.stringify(args.concepts)}`];
118+
}
119+
if (args.objects) {
120+
moveArgs = [...moveArgs, `objects:${parseMoveObjects(move, args.objects)}`];
121+
}
122+
if (args.force) {
123+
moveArgs = [...moveArgs, `force:${args.force}`];
124+
}
125+
return `${move}:{${moveArgs.join(',')}}`;
126+
}

0 commit comments

Comments
 (0)