Skip to content

Commit d189734

Browse files
authored
chore: When querying with vectorSearch use the generated embeddings MCP-245 (#662)
1 parent ebe3d9f commit d189734

File tree

10 files changed

+981
-154
lines changed

10 files changed

+981
-154
lines changed

package-lock.json

Lines changed: 25 additions & 138 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
"@typescript-eslint/parser": "^8.44.0",
7777
"@vitest/coverage-v8": "^3.2.4",
7878
"@vitest/eslint-plugin": "^1.3.4",
79-
"ai": "^5.0.72",
8079
"duplexpair": "^1.0.2",
8180
"eslint": "^9.34.0",
8281
"eslint-config-prettier": "^10.1.8",
@@ -104,6 +103,7 @@
104103
"@mongodb-js/devtools-proxy-support": "^0.5.3",
105104
"@mongosh/arg-parser": "^3.19.0",
106105
"@mongosh/service-provider-node-driver": "^3.17.0",
106+
"ai": "^5.0.72",
107107
"bson": "^6.10.4",
108108
"express": "^5.1.0",
109109
"lru-cache": "^11.1.0",
@@ -116,6 +116,7 @@
116116
"oauth4webapi": "^3.8.0",
117117
"openapi-fetch": "^0.14.0",
118118
"ts-levenshtein": "^1.0.7",
119+
"voyage-ai-provider": "^2.0.0",
119120
"yargs-parser": "21.1.1",
120121
"zod": "^3.25.76"
121122
},

src/common/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export enum ErrorCodes {
44
ForbiddenCollscan = 1_000_002,
55
ForbiddenWriteOperation = 1_000_003,
66
AtlasSearchNotSupported = 1_000_004,
7+
NoEmbeddingsProviderConfigured = 1_000_005,
8+
AtlasVectorSearchIndexNotFound = 1_000_006,
9+
AtlasVectorSearchInvalidQuery = 1_000_007,
710
}
811

912
export class MongoDBError<ErrorCode extends ErrorCodes = ErrorCodes> extends Error {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { createVoyage } from "voyage-ai-provider";
2+
import type { VoyageProvider } from "voyage-ai-provider";
3+
import { embedMany } from "ai";
4+
import type { UserConfig } from "../config.js";
5+
import assert from "assert";
6+
import { createFetch } from "@mongodb-js/devtools-proxy-support";
7+
import { z } from "zod";
8+
9+
type EmbeddingsInput = string;
10+
type Embeddings = number[];
11+
export type EmbeddingParameters = {
12+
inputType: "query" | "document";
13+
};
14+
15+
export interface EmbeddingsProvider<
16+
SupportedModels extends string,
17+
SupportedEmbeddingParameters extends EmbeddingParameters,
18+
> {
19+
embed(
20+
modelId: SupportedModels,
21+
content: EmbeddingsInput[],
22+
parameters: SupportedEmbeddingParameters
23+
): Promise<Embeddings[]>;
24+
}
25+
26+
export const zVoyageModels = z
27+
.enum(["voyage-3-large", "voyage-3.5", "voyage-3.5-lite", "voyage-code-3"])
28+
.default("voyage-3-large");
29+
30+
export const zVoyageEmbeddingParameters = z.object({
31+
outputDimension: z
32+
.union([z.literal(256), z.literal(512), z.literal(1024), z.literal(2048), z.literal(4096)])
33+
.optional()
34+
.default(1024),
35+
outputDType: z.enum(["float", "int8", "uint8", "binary", "ubinary"]).optional().default("float"),
36+
});
37+
38+
type VoyageModels = z.infer<typeof zVoyageModels>;
39+
type VoyageEmbeddingParameters = z.infer<typeof zVoyageEmbeddingParameters> & EmbeddingParameters;
40+
41+
class VoyageEmbeddingsProvider implements EmbeddingsProvider<VoyageModels, VoyageEmbeddingParameters> {
42+
private readonly voyage: VoyageProvider;
43+
44+
constructor({ voyageApiKey }: UserConfig, providedFetch?: typeof fetch) {
45+
assert(voyageApiKey, "The VoyageAI API Key does not exist. This is likely a bug.");
46+
47+
// We should always use, by default, any enterprise proxy that the user has configured.
48+
// Direct requests to VoyageAI might get blocked by the network if they don't go through
49+
// the provided proxy.
50+
const customFetch: typeof fetch = (providedFetch ??
51+
createFetch({ useEnvironmentVariableProxies: true })) as unknown as typeof fetch;
52+
53+
this.voyage = createVoyage({ apiKey: voyageApiKey, fetch: customFetch });
54+
}
55+
56+
static isConfiguredIn({ voyageApiKey }: UserConfig): boolean {
57+
return !!voyageApiKey;
58+
}
59+
60+
async embed<Model extends VoyageModels>(
61+
modelId: Model,
62+
content: EmbeddingsInput[],
63+
parameters: VoyageEmbeddingParameters
64+
): Promise<Embeddings[]> {
65+
const model = this.voyage.textEmbeddingModel(modelId);
66+
const { embeddings } = await embedMany({
67+
model,
68+
values: content,
69+
providerOptions: { voyage: parameters },
70+
});
71+
72+
return embeddings;
73+
}
74+
}
75+
76+
export function getEmbeddingsProvider(
77+
userConfig: UserConfig
78+
): EmbeddingsProvider<VoyageModels, VoyageEmbeddingParameters> | undefined {
79+
if (VoyageEmbeddingsProvider.isConfiguredIn(userConfig)) {
80+
return new VoyageEmbeddingsProvider(userConfig);
81+
}
82+
83+
return undefined;
84+
}
85+
86+
export const zSupportedEmbeddingParameters = zVoyageEmbeddingParameters.extend({ model: zVoyageModels });
87+
export type SupportedEmbeddingParameters = z.infer<typeof zSupportedEmbeddingParameters>;

0 commit comments

Comments
 (0)