UNPKG

voyage-ai-provider

Version:

Voyage AI Provider for running Voyage AI models with Vercel AI SDK

478 lines (466 loc) 15.6 kB
// src/voyage-provider.ts import { loadApiKey, withoutTrailingSlash } from "@ai-sdk/provider-utils"; // src/voyage-embedding-model.ts import { TooManyEmbeddingValuesForCallError } from "@ai-sdk/provider"; import { combineHeaders, createJsonResponseHandler, parseProviderOptions, postJsonToApi } from "@ai-sdk/provider-utils"; import { z as z3 } from "zod/v4"; // src/voyage-embedding-settings.ts import { z } from "zod/v4"; var voyageEmbeddingOptions = z.object({ /** * The input type for the embeddings. Defaults to "query". * For query, the prompt is "Represent the query for retrieving supporting documents: ". * For document, the prompt is "Represent the document for retrieval: ". */ inputType: z.enum(["query", "document"]).optional(), // /** // * Format in which the embeddings are encoded. We support two options: // * If not specified (defaults to null): the embeddings are represented as lists of floating-point numbers; // * base64: the embeddings are compressed to base64 encodings. // */ // encodingFormat?: 'base64'; /** * The number of dimensions for the resulting output embeddings. * * If not specified (defaults to null), the resulting output embeddings dimension is the default for the model. * `voyage-code-3` supports the following `outputDimension` values: 2048, 1024 (default), 512, and 256. * `voyage-3-large` supports the following `outputDimension` values: 2048, 1024 (default), 512, and 256. * * please refer to the model documentation for the supported values. * https://docs.voyageai.com/docs/embeddings */ outputDimension: z.number().optional(), /** * The data type for the resulting output embeddings. * * Defaults to 'float'. * * Other options: 'int8', 'uint8', 'binary', 'ubinary'. * - 'float' is supported by all models. * - 'float': Each returned embedding is a list of 32-bit (4-byte) single-precision floating-point numbers. * - 'int8', 'uint8', 'binary', and 'ubinary' are supported by 'voyage-code-3'. * - 'int8' and 'uint8': Each returned embedding is a list of 8-bit (1-byte) integers ranging from -128 to 127 and 0 to 255, respectively. * - 'binary' and 'ubinary': Each returned embedding is a list of 8-bit integers that represent bit-packed, quantized single-bit embedding values: * 'int8' for 'binary' and 'uint8' for 'ubinary'. * The length of the returned list of integers is 1/8 of outputDimension (which is the actual dimension of the embedding). * The 'binary' type uses the offset binary method. * * https://docs.voyageai.com/docs/faq#what-is-quantization-and-output-data-types */ outputDtype: z.enum(["float", "int8", "uint8", "binary", "ubinary"]).optional(), /** * Whether to truncate the input texts to fit within the context length. */ truncation: z.boolean().optional() }); // src/voyage-error.ts import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils"; import { z as z2 } from "zod/v4"; var voyageErrorDataSchema = z2.object({ error: z2.object({ code: z2.string().nullable(), message: z2.string(), param: z2.any().nullable(), type: z2.string() }) }); var voyageFailedResponseHandler = createJsonErrorResponseHandler({ errorSchema: voyageErrorDataSchema, errorToMessage: (data) => data.error.message }); // src/voyage-embedding-model.ts var VoyageEmbeddingModel = class { specificationVersion = "v3"; modelId; config; get provider() { return this.config.provider; } get maxEmbeddingsPerCall() { return 128; } get supportsParallelCalls() { return false; } constructor(modelId, config) { this.modelId = modelId; this.config = config; } async doEmbed({ abortSignal, values, headers, providerOptions }) { const embeddingOptions = await parseProviderOptions({ provider: "voyage", providerOptions, schema: voyageEmbeddingOptions }); if (values.length > this.maxEmbeddingsPerCall) { throw new TooManyEmbeddingValuesForCallError({ maxEmbeddingsPerCall: this.maxEmbeddingsPerCall, modelId: this.modelId, provider: this.provider, values }); } const { responseHeaders, value: response, rawValue } = await postJsonToApi({ abortSignal, body: { input: values, model: this.modelId, input_type: embeddingOptions?.inputType, truncation: embeddingOptions?.truncation, output_dimension: embeddingOptions?.outputDimension, output_dtype: embeddingOptions?.outputDtype }, failedResponseHandler: voyageFailedResponseHandler, fetch: this.config.fetch, headers: combineHeaders(this.config.headers(), headers), successfulResponseHandler: createJsonResponseHandler( voyageTextEmbeddingResponseSchema ), url: `${this.config.baseURL}/embeddings` }); return { embeddings: response.data.map((item) => item.embedding), usage: response.usage ? { tokens: response.usage.total_tokens } : void 0, response: { headers: responseHeaders, body: rawValue }, warnings: [] }; } }; var voyageTextEmbeddingResponseSchema = z3.object({ data: z3.array(z3.object({ embedding: z3.array(z3.number()) })), usage: z3.object({ total_tokens: z3.number() }).nullish() }); // src/voyage-multimodal-embedding-model.ts import { TooManyEmbeddingValuesForCallError as TooManyEmbeddingValuesForCallError2 } from "@ai-sdk/provider"; import { combineHeaders as combineHeaders2, createJsonResponseHandler as createJsonResponseHandler2, parseProviderOptions as parseProviderOptions2, postJsonToApi as postJsonToApi2 } from "@ai-sdk/provider-utils"; // src/voyage-multimodal-embedding-settings.ts import { z as z4 } from "zod/v4"; var voyageMultimodalContentPart = z4.union([ z4.object({ type: z4.literal("text"), text: z4.string() }), z4.object({ type: z4.literal("image_url"), image_url: z4.string() }), z4.object({ type: z4.literal("image_base64"), image_base64: z4.string() }) ]); var voyageMultimodalEmbeddingOptions = z4.object({ /** * Type of the input. * Defaults to "query". * * When input_type is specified as "query" or "document", Voyage automatically prepends a prompt * to your inputs before vectorize them, creating vectors more tailored for retrieval/search tasks. * * For retrieval/search purposes where a query is used to search through documents, we recommend * specifying whether your inputs are queries or documents. Since inputs can be multimodal, * "queries" and "documents" can be text, images, or an interleaving of both modalities. * * For transparency, the following prompts are prepended: * - For "query": "Represent the query for retrieving supporting documents: " * - For "document": "Represent the document for retrieval: " */ inputType: z4.enum(["query", "document"]).optional(), /** * The data type for the resulting output embeddings. * * Defaults to null. * * - If null, the embeddings are represented as a list of floating-point numbers. * - If base64, the embeddings are represented as a Base64-encoded NumPy array of single-precision floats. * * https://docs.voyageai.com/docs/faq#what-is-quantization-and-output-data-types */ outputEncoding: z4.enum(["base64"]).optional(), /** * Whether to truncate the input texts to fit within the context length. * * Defaults to true. */ truncation: z4.boolean().optional(), /** * Per-value multimodal content parts for embedding non-text content * (images). Each entry corresponds to the embedding value at the same * index and its parts are merged after the text value in the request. * Use `null` for entries that are text-only. * * The array length must match the number of values being embedded. * * https://docs.voyageai.com/reference/multimodal-embeddings-api */ content: z4.array(z4.array(voyageMultimodalContentPart).min(1).nullable()).optional() }); // src/voyage-multimodal-embedding-model.ts import { z as z5 } from "zod/v4"; var MultimodalEmbeddingModel = class { specificationVersion = "v3"; modelId; config; get provider() { return this.config.provider; } get maxEmbeddingsPerCall() { return 128; } get supportsParallelCalls() { return false; } constructor(modelId, config) { this.modelId = modelId; this.config = config; } async doEmbed({ abortSignal, values, headers, providerOptions }) { const embeddingOptions = await parseProviderOptions2({ provider: "voyage", providerOptions, schema: voyageMultimodalEmbeddingOptions }); if (values.length > this.maxEmbeddingsPerCall) { throw new TooManyEmbeddingValuesForCallError2({ maxEmbeddingsPerCall: this.maxEmbeddingsPerCall, modelId: this.modelId, provider: this.provider, values }); } const multimodalContent = embeddingOptions?.content; if (multimodalContent != null && multimodalContent.length !== values.length) { throw new Error( `The number of multimodal content entries (${multimodalContent.length}) must match the number of values (${values.length}).` ); } const inputs = values.map((value, index) => { const valueParts = multimodalContent?.[index]; const textPart = value ? [{ type: "text", text: value }] : []; return { content: valueParts != null ? [...textPart, ...valueParts] : [{ type: "text", text: value }] }; }); const { responseHeaders, value: response, rawValue } = await postJsonToApi2({ abortSignal, body: { inputs, model: this.modelId, input_type: embeddingOptions?.inputType, truncation: embeddingOptions?.truncation, output_encoding: embeddingOptions?.outputEncoding }, failedResponseHandler: voyageFailedResponseHandler, fetch: this.config.fetch, headers: combineHeaders2(this.config.headers(), headers), successfulResponseHandler: createJsonResponseHandler2( voyageMultimodalEmbeddingResponseSchema ), url: `${this.config.baseURL}/multimodalembeddings` }); return { embeddings: response.data.map((item) => item.embedding), usage: response.usage ? { tokens: response.usage.total_tokens } : void 0, response: { headers: responseHeaders, body: rawValue }, warnings: [] }; } }; var voyageMultimodalEmbeddingResponseSchema = z5.object({ data: z5.array( z5.object({ object: z5.literal("embedding"), embedding: z5.array(z5.number()), index: z5.number() }) ), usage: z5.object({ text_tokens: z5.number().nullish(), image_pixels: z5.number().nullish(), total_tokens: z5.number() }), model: z5.string() }); // src/reranking/voyage-reranking-model.ts import { combineHeaders as combineHeaders3, createJsonResponseHandler as createJsonResponseHandler3, parseProviderOptions as parseProviderOptions3, postJsonToApi as postJsonToApi3 } from "@ai-sdk/provider-utils"; // src/reranking/voyage-reranking-api.ts import { lazySchema, zodSchema } from "@ai-sdk/provider-utils"; import { z as z6 } from "zod/v4"; var voyageRerankingResponseSchema = lazySchema( () => zodSchema( z6.object({ object: z6.literal("list"), data: z6.array( z6.object({ relevance_score: z6.number(), index: z6.number() }) ), model: z6.string(), usage: z6.object({ total_tokens: z6.number() }) }) ) ); // src/reranking/voyage-reranking-options.ts import { lazySchema as lazySchema2, zodSchema as zodSchema2 } from "@ai-sdk/provider-utils"; import { z as z7 } from "zod/v4"; var voyageRerankingOptionsSchema = lazySchema2( () => zodSchema2( z7.object({ returnDocuments: z7.boolean().optional(), truncation: z7.boolean().optional() }) ) ); // src/reranking/voyage-reranking-model.ts var VoyageRerankingModel = class { specificationVersion = "v3"; modelId; config; constructor(modelId, config) { this.modelId = modelId; this.config = config; } get provider() { return this.config.provider; } // current implementation is based on the API:https://docs.voyageai.com/reference/reranker-api async doRerank({ documents, headers, query, topN, abortSignal, providerOptions }) { const rerankingOptions = await parseProviderOptions3({ provider: "voyage", providerOptions, schema: voyageRerankingOptionsSchema }); const warnings = []; const { responseHeaders, value: response, rawValue } = await postJsonToApi3({ url: `${this.config.baseURL}/rerank`, headers: combineHeaders3(this.config.headers(), headers), body: { model: this.modelId, query, documents: documents.type === "text" ? documents.values : documents.values.map((value) => JSON.stringify(value)), top_k: topN, return_documents: rerankingOptions?.returnDocuments ?? false, truncation: rerankingOptions?.truncation ?? true }, failedResponseHandler: voyageFailedResponseHandler, successfulResponseHandler: createJsonResponseHandler3( voyageRerankingResponseSchema ), abortSignal, fetch: this.config.fetch }); return { ranking: response.data.map((result) => ({ index: result.index, relevanceScore: result.relevance_score })), warnings: warnings.length > 0 ? warnings : void 0, response: { headers: responseHeaders, body: rawValue } }; } }; // src/voyage-provider.ts function createVoyage(options = {}) { const baseURL = withoutTrailingSlash(options.baseURL) ?? "https://api.voyageai.com/v1"; const getHeaders = () => ({ Authorization: `Bearer ${loadApiKey({ apiKey: options.apiKey, environmentVariableName: "VOYAGE_API_KEY", description: "Voyage" })}`, ...options.headers }); const createEmbeddingModel = (modelId) => new VoyageEmbeddingModel(modelId, { provider: "voyage.embedding", baseURL, headers: getHeaders, fetch: options.fetch }); const createMultimodalEmbeddingModel = (modelId) => new MultimodalEmbeddingModel(modelId, { provider: "voyage.multimodal.embedding", baseURL, headers: getHeaders, fetch: options.fetch }); const createRerankingModel = (modelId) => new VoyageRerankingModel(modelId, { provider: "voyage.reranking", baseURL, headers: getHeaders, fetch: options.fetch }); const provider = function(modelId) { if (new.target) { throw new Error( "The Voyage model function cannot be called with the new keyword." ); } return createEmbeddingModel(modelId); }; provider.embeddingModel = createEmbeddingModel; provider.textEmbeddingModel = createEmbeddingModel; provider.multimodalEmbeddingModel = createMultimodalEmbeddingModel; provider.imageEmbeddingModel = createMultimodalEmbeddingModel; provider.chat = provider.languageModel = () => { throw new Error("languageModel method is not implemented."); }; provider.imageModel = () => { throw new Error("imageModel method is not implemented."); }; provider.reranking = createRerankingModel; provider.rerankingModel = createRerankingModel; return provider; } var voyage = createVoyage(); export { createVoyage, voyage }; //# sourceMappingURL=index.js.map