From 37525df5292a3eafc83d6928d2873e68a0e06b62 Mon Sep 17 00:00:00 2001 From: Parham Saidi <parham@parha.me> Date: Mon, 20 May 2024 12:09:25 +0200 Subject: [PATCH] feat: Gemini Access via Vertex AI (#838) --- .changeset/fuzzy-coins-breathe.md | 6 + .../modules/llms/available_llms/gemini.md | 30 ++ examples/gemini/chatVertex.ts | 19 + .../examples/02_nextjs/next.config.mjs | 3 +- packages/core/package.json | 1 + .../core/src/embeddings/GeminiEmbedding.ts | 12 +- packages/core/src/index.ts | 3 + packages/core/src/llm/gemini.ts | 341 ------------------ packages/core/src/llm/gemini/base.ts | 248 +++++++++++++ packages/core/src/llm/gemini/types.ts | 111 ++++++ packages/core/src/llm/gemini/utils.ts | 197 ++++++++++ packages/core/src/llm/gemini/vertex.ts | 81 +++++ packages/core/src/llm/index.ts | 8 +- packages/core/src/llm/utils.ts | 21 ++ packages/core/src/next.ts | 1 + pnpm-lock.yaml | 145 +++++++- 16 files changed, 878 insertions(+), 349 deletions(-) create mode 100644 .changeset/fuzzy-coins-breathe.md create mode 100644 examples/gemini/chatVertex.ts delete mode 100644 packages/core/src/llm/gemini.ts create mode 100644 packages/core/src/llm/gemini/base.ts create mode 100644 packages/core/src/llm/gemini/types.ts create mode 100644 packages/core/src/llm/gemini/utils.ts create mode 100644 packages/core/src/llm/gemini/vertex.ts diff --git a/.changeset/fuzzy-coins-breathe.md b/.changeset/fuzzy-coins-breathe.md new file mode 100644 index 000000000..3edba9119 --- /dev/null +++ b/.changeset/fuzzy-coins-breathe.md @@ -0,0 +1,6 @@ +--- +"llamaindex": patch +"@llamaindex/examples": patch +--- + +Added support for accessing Gemini via Vertex AI diff --git a/apps/docs/docs/modules/llms/available_llms/gemini.md b/apps/docs/docs/modules/llms/available_llms/gemini.md index cfd7698eb..bc04d46bd 100644 --- a/apps/docs/docs/modules/llms/available_llms/gemini.md +++ b/apps/docs/docs/modules/llms/available_llms/gemini.md @@ -10,6 +10,36 @@ Settings.llm = new Gemini({ }); ``` +### Usage with Vertex AI + +To use Gemini via Vertex AI you can use `GeminiVertexSession`. + +GeminiVertexSession accepts the env variables: `GOOGLE_VERTEX_LOCATION` and `GOOGLE_VERTEX_PROJECT` + +```ts +import { Gemini, GEMINI_MODEL, GeminiVertexSession } from "llamaindex"; + +const gemini = new Gemini({ + model: GEMINI_MODEL.GEMINI_PRO, + session: new GeminiVertexSession({ + location: "us-central1", // optional if provided by GOOGLE_VERTEX_LOCATION env variable + project: "project1", // optional if provided by GOOGLE_VERTEX_PROJECT env variable + googleAuthOptions: {...}, // optional, but useful for production. It accepts all values from `GoogleAuthOptions` + }), +}); +``` + +[GoogleAuthOptions](https://github.com/googleapis/google-auth-library-nodejs/blob/main/src/auth/googleauth.ts) + +To authenticate for local development: + +```bash +npm install @google-cloud/vertexai +gcloud auth application-default login +``` + +To authenticate for production you'll have to use a [service account](https://cloud.google.com/docs/authentication/). `googleAuthOptions` has `credentials` which might be useful for you. + ## Load and index documents For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. diff --git a/examples/gemini/chatVertex.ts b/examples/gemini/chatVertex.ts new file mode 100644 index 000000000..47936aaf2 --- /dev/null +++ b/examples/gemini/chatVertex.ts @@ -0,0 +1,19 @@ +import { Gemini, GEMINI_MODEL, GeminiVertexSession } from "llamaindex"; + +(async () => { + const gemini = new Gemini({ + model: GEMINI_MODEL.GEMINI_PRO, + session: new GeminiVertexSession(), + }); + const result = await gemini.chat({ + messages: [ + { content: "You want to talk in rhymes.", role: "system" }, + { + content: + "How much wood would a woodchuck chuck if a woodchuck could chuck wood?", + role: "user", + }, + ], + }); + console.log(result); +})(); diff --git a/packages/autotool/examples/02_nextjs/next.config.mjs b/packages/autotool/examples/02_nextjs/next.config.mjs index 02a508a86..550bd5eed 100644 --- a/packages/autotool/examples/02_nextjs/next.config.mjs +++ b/packages/autotool/examples/02_nextjs/next.config.mjs @@ -1,6 +1,7 @@ import { withNext } from "@llamaindex/autotool/next"; +import withLlamaIndex from "llamaindex/next"; /** @type {import('next').NextConfig} */ const nextConfig = {}; -export default withNext(nextConfig); +export default withLlamaIndex(withNext(nextConfig)); diff --git a/packages/core/package.json b/packages/core/package.json index d32acbbe3..8bbe94042 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,6 +24,7 @@ "@anthropic-ai/sdk": "^0.20.9", "@aws-crypto/sha256-js": "^5.2.0", "@datastax/astra-db-ts": "^1.1.0", + "@google-cloud/vertexai": "^1.1.0", "@google/generative-ai": "^0.11.0", "@grpc/grpc-js": "^1.10.7", "@huggingface/inference": "^2.6.7", diff --git a/packages/core/src/embeddings/GeminiEmbedding.ts b/packages/core/src/embeddings/GeminiEmbedding.ts index 1b4fda6bc..fad773942 100644 --- a/packages/core/src/embeddings/GeminiEmbedding.ts +++ b/packages/core/src/embeddings/GeminiEmbedding.ts @@ -1,4 +1,5 @@ -import { GeminiSessionStore, type GeminiSession } from "../llm/gemini.js"; +import { GeminiSession, GeminiSessionStore } from "../llm/gemini/base.js"; +import { GEMINI_BACKENDS } from "../llm/gemini/types.js"; import { BaseEmbedding } from "./types.js"; export enum GEMINI_EMBEDDING_MODEL { @@ -8,6 +9,7 @@ export enum GEMINI_EMBEDDING_MODEL { /** * GeminiEmbedding is an alias for Gemini that implements the BaseEmbedding interface. + * Note: Vertex SDK currently does not support embeddings */ export class GeminiEmbedding extends BaseEmbedding { model: GEMINI_EMBEDDING_MODEL; @@ -16,11 +18,15 @@ export class GeminiEmbedding extends BaseEmbedding { constructor(init?: Partial<GeminiEmbedding>) { super(); this.model = init?.model ?? GEMINI_EMBEDDING_MODEL.EMBEDDING_001; - this.session = init?.session ?? GeminiSessionStore.get(); + this.session = + init?.session ?? + (GeminiSessionStore.get({ + backend: GEMINI_BACKENDS.GOOGLE, + }) as GeminiSession); } private async getEmbedding(prompt: string): Promise<number[]> { - const client = this.session.gemini.getGenerativeModel({ + const client = this.session.getGenerativeModel({ model: this.model, }); const result = await client.embedContent(prompt); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 30728ee78..41c1e83dd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,3 +10,6 @@ export { HuggingFaceEmbedding, HuggingFaceEmbeddingModelType, } from "./embeddings/HuggingFaceEmbedding.js"; + +export { type VertexGeminiSessionOptions } from "./llm/gemini/types.js"; +export { GeminiVertexSession } from "./llm/gemini/vertex.js"; diff --git a/packages/core/src/llm/gemini.ts b/packages/core/src/llm/gemini.ts deleted file mode 100644 index 9b98fcd24..000000000 --- a/packages/core/src/llm/gemini.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { - ChatSession, - GoogleGenerativeAI, - type Content as GeminiMessageContent, - type Part, -} from "@google/generative-ai"; -import { getEnv } from "@llamaindex/env"; -import { ToolCallLLM } from "./base.js"; -import type { - ChatMessage, - ChatResponse, - ChatResponseChunk, - CompletionResponse, - LLMChatParamsNonStreaming, - LLMChatParamsStreaming, - LLMCompletionParamsNonStreaming, - LLMCompletionParamsStreaming, - LLMMetadata, - MessageContent, - MessageContentImageDetail, - MessageContentTextDetail, - MessageType, - ToolCallLLMMessageOptions, -} from "./types.js"; -import { streamConverter, wrapLLMEvent } from "./utils.js"; - -// Session and Model Type Definitions -type GeminiSessionOptions = { - apiKey?: string; -}; - -export enum GEMINI_MODEL { - GEMINI_PRO = "gemini-pro", - GEMINI_PRO_VISION = "gemini-pro-vision", - GEMINI_PRO_LATEST = "gemini-1.5-pro-latest", -} - -export interface GeminiModelInfo { - contextWindow: number; -} - -export const GEMINI_MODEL_INFO_MAP: Record<GEMINI_MODEL, GeminiModelInfo> = { - [GEMINI_MODEL.GEMINI_PRO]: { contextWindow: 30720 }, - [GEMINI_MODEL.GEMINI_PRO_VISION]: { contextWindow: 12288 }, - [GEMINI_MODEL.GEMINI_PRO_LATEST]: { contextWindow: 10 ** 6 }, -}; - -const SUPPORT_TOOL_CALL_MODELS: GEMINI_MODEL[] = [ - GEMINI_MODEL.GEMINI_PRO, - GEMINI_MODEL.GEMINI_PRO_VISION, -]; - -const DEFAULT_GEMINI_PARAMS = { - model: GEMINI_MODEL.GEMINI_PRO, - temperature: 0.1, - topP: 1, - maxTokens: undefined, -}; - -export type GeminiConfig = Partial<typeof DEFAULT_GEMINI_PARAMS> & { - session?: GeminiSession; -}; - -/// Chat Type Definitions -type GeminiMessageRole = "user" | "model"; - -export type GeminiAdditionalChatOptions = {}; - -export type GeminiChatParamsStreaming = LLMChatParamsStreaming< - GeminiAdditionalChatOptions, - ToolCallLLMMessageOptions ->; - -export type GeminiChatStreamResponse = AsyncIterable< - ChatResponseChunk<ToolCallLLMMessageOptions> ->; - -export type GeminiChatParamsNonStreaming = LLMChatParamsNonStreaming< - GeminiAdditionalChatOptions, - ToolCallLLMMessageOptions ->; - -export type GeminiChatNonStreamResponse = - ChatResponse<ToolCallLLMMessageOptions>; - -/** - * Gemini Session to manage the connection to the Gemini API - */ -export class GeminiSession { - gemini: GoogleGenerativeAI; - - constructor(options: GeminiSessionOptions) { - if (!options.apiKey) { - options.apiKey = getEnv("GOOGLE_API_KEY"); - } - if (!options.apiKey) { - throw new Error("Set Google API Key in GOOGLE_API_KEY env variable"); - } - this.gemini = new GoogleGenerativeAI(options.apiKey); - } -} - -/** - * Gemini Session Store to manage the current Gemini sessions - */ -export class GeminiSessionStore { - static sessions: Array<{ - session: GeminiSession; - options: GeminiSessionOptions; - }> = []; - - private static sessionMatched( - o1: GeminiSessionOptions, - o2: GeminiSessionOptions, - ): boolean { - return o1.apiKey === o2.apiKey; - } - - static get(options: GeminiSessionOptions = {}): GeminiSession { - let session = this.sessions.find((session) => - this.sessionMatched(session.options, options), - )?.session; - if (!session) { - session = new GeminiSession(options); - this.sessions.push({ session, options }); - } - return session; - } -} - -/** - * Helper class providing utility functions for Gemini - */ -class GeminiHelper { - // Gemini only has user and model roles. Put the rest in user role. - public static readonly ROLES_TO_GEMINI: Record< - MessageType, - GeminiMessageRole - > = { - user: "user", - system: "user", - assistant: "model", - memory: "user", - }; - - public static readonly ROLES_FROM_GEMINI: Record< - GeminiMessageRole, - MessageType - > = { - user: "user", - model: "assistant", - }; - - public static mergeNeighboringSameRoleMessages( - messages: GeminiMessageContent[], - ): GeminiMessageContent[] { - return messages.reduce( - ( - result: GeminiMessageContent[], - current: GeminiMessageContent, - index: number, - ) => { - if (index > 0 && messages[index - 1].role === current.role) { - result[result.length - 1].parts = [ - ...result[result.length - 1].parts, - ...current.parts, - ]; - } else { - result.push(current); - } - return result; - }, - [], - ); - } - - public static messageContentToGeminiParts(content: MessageContent): Part[] { - if (typeof content === "string") { - return [{ text: content }]; - } - - const parts: Part[] = []; - const imageContents = content.filter( - (i) => i.type === "image_url", - ) as MessageContentImageDetail[]; - parts.push( - ...imageContents.map((i) => ({ - fileData: { - mimeType: i.type, - fileUri: i.image_url.url, - }, - })), - ); - const textContents = content.filter( - (i) => i.type === "text", - ) as MessageContentTextDetail[]; - parts.push(...textContents.map((t) => ({ text: t.text }))); - return parts; - } - - public static chatMessageToGemini( - message: ChatMessage, - ): GeminiMessageContent { - return { - role: GeminiHelper.ROLES_TO_GEMINI[message.role], - parts: GeminiHelper.messageContentToGeminiParts(message.content), - }; - } -} - -/** - * ToolCallLLM for Gemini - */ -export class Gemini extends ToolCallLLM<GeminiAdditionalChatOptions> { - model: GEMINI_MODEL; - temperature: number; - topP: number; - maxTokens?: number; - session: GeminiSession; - - constructor(init?: GeminiConfig) { - super(); - this.model = init?.model ?? GEMINI_MODEL.GEMINI_PRO; - this.temperature = init?.temperature ?? 0.1; - this.topP = init?.topP ?? 1; - this.maxTokens = init?.maxTokens ?? undefined; - this.session = init?.session ?? GeminiSessionStore.get(); - } - - get supportToolCall(): boolean { - return SUPPORT_TOOL_CALL_MODELS.includes(this.model); - } - - get metadata(): LLMMetadata { - return { - model: this.model, - temperature: this.temperature, - topP: this.topP, - maxTokens: this.maxTokens, - contextWindow: GEMINI_MODEL_INFO_MAP[this.model].contextWindow, - tokenizer: undefined, - }; - } - - private prepareChat( - params: GeminiChatParamsStreaming | GeminiChatParamsNonStreaming, - ): { - chat: ChatSession; - messageContent: Part[]; - } { - const messages = GeminiHelper.mergeNeighboringSameRoleMessages( - params.messages.map(GeminiHelper.chatMessageToGemini), - ); - - const history = messages.slice(0, -1); - - const client = this.session.gemini.getGenerativeModel(this.metadata); - - const chat = client.startChat({ - history, - }); - return { - chat, - messageContent: messages[messages.length - 1].parts, - }; - } - - protected async nonStreamChat( - params: GeminiChatParamsNonStreaming, - ): Promise<GeminiChatNonStreamResponse> { - const { chat, messageContent } = this.prepareChat(params); - const result = await chat.sendMessage(messageContent); - const { response } = result; - const topCandidate = response.candidates![0]; - return { - raw: response, - message: { - content: response.text(), - role: GeminiHelper.ROLES_FROM_GEMINI[ - topCandidate.content.role as GeminiMessageRole - ], - }, - }; - } - - protected async *streamChat( - params: GeminiChatParamsStreaming, - ): GeminiChatStreamResponse { - const { chat, messageContent } = this.prepareChat(params); - const result = await chat.sendMessageStream(messageContent); - yield* streamConverter(result.stream, (response) => ({ - delta: response.text(), - raw: response, - })); - } - - chat(params: GeminiChatParamsStreaming): Promise<GeminiChatStreamResponse>; - chat( - params: GeminiChatParamsNonStreaming, - ): Promise<GeminiChatNonStreamResponse>; - @wrapLLMEvent - async chat( - params: GeminiChatParamsStreaming | GeminiChatParamsNonStreaming, - ): Promise<GeminiChatStreamResponse | GeminiChatNonStreamResponse> { - if (params.stream) return this.streamChat(params); - return this.nonStreamChat(params); - } - - complete( - params: LLMCompletionParamsStreaming, - ): Promise<AsyncIterable<CompletionResponse>>; - complete( - params: LLMCompletionParamsNonStreaming, - ): Promise<CompletionResponse>; - async complete( - params: LLMCompletionParamsStreaming | LLMCompletionParamsNonStreaming, - ): Promise<CompletionResponse | AsyncIterable<CompletionResponse>> { - const { prompt, stream } = params; - const client = this.session.gemini.getGenerativeModel(this.metadata); - - if (stream) { - const result = await client.generateContentStream( - GeminiHelper.messageContentToGeminiParts(prompt), - ); - return streamConverter(result.stream, (response) => { - return { - text: response.text(), - raw: response, - }; - }); - } - - const result = await client.generateContent( - GeminiHelper.messageContentToGeminiParts(prompt), - ); - return { - text: result.response.text(), - raw: result.response, - }; - } -} diff --git a/packages/core/src/llm/gemini/base.ts b/packages/core/src/llm/gemini/base.ts new file mode 100644 index 000000000..c959c2732 --- /dev/null +++ b/packages/core/src/llm/gemini/base.ts @@ -0,0 +1,248 @@ +import { + GoogleGenerativeAI, + GenerativeModel as GoogleGenerativeModel, + type EnhancedGenerateContentResponse, + type ModelParams as GoogleModelParams, + type GenerateContentStreamResult as GoogleStreamGenerateContentResult, +} from "@google/generative-ai"; + +import { getEnv } from "@llamaindex/env"; +import { ToolCallLLM } from "../base.js"; +import type { + CompletionResponse, + LLMCompletionParamsNonStreaming, + LLMCompletionParamsStreaming, + LLMMetadata, +} from "../types.js"; +import { streamConverter, wrapLLMEvent } from "../utils.js"; +import { + GEMINI_BACKENDS, + GEMINI_MODEL, + type GeminiAdditionalChatOptions, + type GeminiChatNonStreamResponse, + type GeminiChatParamsNonStreaming, + type GeminiChatParamsStreaming, + type GeminiChatStreamResponse, + type GeminiMessageRole, + type GeminiModelInfo, + type GeminiSessionOptions, + type GoogleGeminiSessionOptions, + type IGeminiSession, +} from "./types.js"; +import { GeminiHelper, getChatContext, getPartsText } from "./utils.js"; + +export const GEMINI_MODEL_INFO_MAP: Record<GEMINI_MODEL, GeminiModelInfo> = { + [GEMINI_MODEL.GEMINI_PRO]: { contextWindow: 30720 }, + [GEMINI_MODEL.GEMINI_PRO_VISION]: { contextWindow: 12288 }, + [GEMINI_MODEL.GEMINI_PRO_LATEST]: { contextWindow: 10 ** 6 }, +}; + +const SUPPORT_TOOL_CALL_MODELS: GEMINI_MODEL[] = [ + GEMINI_MODEL.GEMINI_PRO, + GEMINI_MODEL.GEMINI_PRO_VISION, +]; + +const DEFAULT_GEMINI_PARAMS = { + model: GEMINI_MODEL.GEMINI_PRO, + temperature: 0.1, + topP: 1, + maxTokens: undefined, +}; + +export type GeminiConfig = Partial<typeof DEFAULT_GEMINI_PARAMS> & { + session?: IGeminiSession; +}; + +/** + * Gemini Session to manage the connection to the Gemini API + */ +export class GeminiSession implements IGeminiSession { + private gemini: GoogleGenerativeAI; + + constructor(options: GoogleGeminiSessionOptions) { + if (!options.apiKey) { + options.apiKey = getEnv("GOOGLE_API_KEY"); + } + if (!options.apiKey) { + throw new Error("Set Google API Key in GOOGLE_API_KEY env variable"); + } + this.gemini = new GoogleGenerativeAI(options.apiKey); + } + + getGenerativeModel(metadata: GoogleModelParams): GoogleGenerativeModel { + return this.gemini.getGenerativeModel(metadata); + } + + getResponseText(response: EnhancedGenerateContentResponse): string { + return response.text(); + } + + async *getChatStream( + result: GoogleStreamGenerateContentResult, + ): GeminiChatStreamResponse { + yield* streamConverter(result.stream, (response) => ({ + delta: this.getResponseText(response), + raw: response, + })); + } + + getCompletionStream( + result: GoogleStreamGenerateContentResult, + ): AsyncIterable<CompletionResponse> { + return streamConverter(result.stream, (response) => ({ + text: this.getResponseText(response), + raw: response, + })); + } +} + +/** + * Gemini Session Store to manage the current Gemini sessions + */ +export class GeminiSessionStore { + static sessions: Array<{ + session: IGeminiSession; + options: GeminiSessionOptions; + }> = []; + + private static getSessionId(options: GeminiSessionOptions): string { + if (options.backend === GEMINI_BACKENDS.GOOGLE) + return options?.apiKey ?? ""; + return ""; + } + private static sessionMatched( + o1: GeminiSessionOptions, + o2: GeminiSessionOptions, + ): boolean { + return ( + GeminiSessionStore.getSessionId(o1) === + GeminiSessionStore.getSessionId(o2) + ); + } + + static get( + options: GeminiSessionOptions = { backend: GEMINI_BACKENDS.GOOGLE }, + ): IGeminiSession { + let session = this.sessions.find((session) => + this.sessionMatched(session.options, options), + )?.session; + if (session) return session; + + if (options.backend === GEMINI_BACKENDS.VERTEX) { + throw Error("No Session"); + } else { + session = new GeminiSession(options); + } + this.sessions.push({ session, options }); + return session; + } +} + +/** + * ToolCallLLM for Gemini + */ +export class Gemini extends ToolCallLLM<GeminiAdditionalChatOptions> { + model: GEMINI_MODEL; + temperature: number; + topP: number; + maxTokens?: number; + session: IGeminiSession; + + constructor(init?: GeminiConfig) { + super(); + this.model = init?.model ?? GEMINI_MODEL.GEMINI_PRO; + this.temperature = init?.temperature ?? 0.1; + this.topP = init?.topP ?? 1; + this.maxTokens = init?.maxTokens ?? undefined; + this.session = init?.session ?? GeminiSessionStore.get(); + } + + get supportToolCall(): boolean { + return SUPPORT_TOOL_CALL_MODELS.includes(this.model); + } + + get metadata(): LLMMetadata { + return { + model: this.model, + temperature: this.temperature, + topP: this.topP, + maxTokens: this.maxTokens, + contextWindow: GEMINI_MODEL_INFO_MAP[this.model].contextWindow, + tokenizer: undefined, + }; + } + + protected async nonStreamChat( + params: GeminiChatParamsNonStreaming, + ): Promise<GeminiChatNonStreamResponse> { + const context = getChatContext(params); + const client = this.session.getGenerativeModel(this.metadata); + const chat = client.startChat({ + history: context.history, + }); + const { response } = await chat.sendMessage(context.message); + const topCandidate = response.candidates![0]; + + return { + raw: response, + message: { + content: this.session.getResponseText(response), + role: GeminiHelper.ROLES_FROM_GEMINI[ + topCandidate.content.role as GeminiMessageRole + ], + }, + }; + } + + protected async *streamChat( + params: GeminiChatParamsStreaming, + ): GeminiChatStreamResponse { + const context = getChatContext(params); + const client = this.session.getGenerativeModel(this.metadata); + const chat = client.startChat({ + history: context.history, + }); + const result = await chat.sendMessageStream(context.message); + yield* this.session.getChatStream(result); + } + + chat(params: GeminiChatParamsStreaming): Promise<GeminiChatStreamResponse>; + chat( + params: GeminiChatParamsNonStreaming, + ): Promise<GeminiChatNonStreamResponse>; + @wrapLLMEvent + async chat( + params: GeminiChatParamsStreaming | GeminiChatParamsNonStreaming, + ): Promise<GeminiChatStreamResponse | GeminiChatNonStreamResponse> { + if (params.stream) return this.streamChat(params); + return this.nonStreamChat(params); + } + + complete( + params: LLMCompletionParamsStreaming, + ): Promise<AsyncIterable<CompletionResponse>>; + complete( + params: LLMCompletionParamsNonStreaming, + ): Promise<CompletionResponse>; + async complete( + params: LLMCompletionParamsStreaming | LLMCompletionParamsNonStreaming, + ): Promise<CompletionResponse | AsyncIterable<CompletionResponse>> { + const { prompt, stream } = params; + const client = this.session.getGenerativeModel(this.metadata); + + if (stream) { + const result = await client.generateContentStream( + getPartsText(GeminiHelper.messageContentToGeminiParts(prompt)), + ); + return this.session.getCompletionStream(result); + } + + const result = await client.generateContent( + getPartsText(GeminiHelper.messageContentToGeminiParts(prompt)), + ); + return { + text: this.session.getResponseText(result.response), + raw: result.response, + }; + } +} diff --git a/packages/core/src/llm/gemini/types.ts b/packages/core/src/llm/gemini/types.ts new file mode 100644 index 000000000..112accea6 --- /dev/null +++ b/packages/core/src/llm/gemini/types.ts @@ -0,0 +1,111 @@ +import { + GenerativeModel as GoogleGenerativeModel, + type EnhancedGenerateContentResponse, + type Content as GeminiMessageContent, + type FileDataPart as GoogleFileDataPart, + type InlineDataPart as GoogleInlineFileDataPart, + type ModelParams as GoogleModelParams, + type Part as GooglePart, + type GenerateContentStreamResult as GoogleStreamGenerateContentResult, +} from "@google/generative-ai"; + +import { + GenerativeModel as VertexGenerativeModel, + GenerativeModelPreview as VertexGenerativeModelPreview, + type GenerateContentResponse, + type FileDataPart as VertexFileDataPart, + type VertexInit, + type InlineDataPart as VertexInlineFileDataPart, + type ModelParams as VertexModelParams, + type Part as VertexPart, + type StreamGenerateContentResult as VertexStreamGenerateContentResult, +} from "@google-cloud/vertexai"; + +import type { + ChatResponse, + ChatResponseChunk, + CompletionResponse, + LLMChatParamsNonStreaming, + LLMChatParamsStreaming, + ToolCallLLMMessageOptions, +} from "../types.js"; + +export enum GEMINI_BACKENDS { + GOOGLE = "google", + VERTEX = "vertex", +} + +export type GoogleGeminiSessionOptions = { + apiKey?: string; +}; + +export type VertexGeminiSessionOptions = { + preview?: boolean; +} & VertexInit; + +export type GeminiSessionOptions = + | (GoogleGeminiSessionOptions & { backend: GEMINI_BACKENDS.GOOGLE }) + | (VertexGeminiSessionOptions & { backend: GEMINI_BACKENDS.VERTEX }); + +export enum GEMINI_MODEL { + GEMINI_PRO = "gemini-pro", + GEMINI_PRO_VISION = "gemini-pro-vision", + GEMINI_PRO_LATEST = "gemini-1.5-pro-latest", +} + +export interface GeminiModelInfo { + contextWindow: number; +} + +export type Part = GooglePart | VertexPart; +export type FileDataPart = GoogleFileDataPart | VertexFileDataPart; +export type InlineDataPart = + | GoogleInlineFileDataPart + | VertexInlineFileDataPart; + +export type ModelParams = GoogleModelParams | VertexModelParams; + +export type GenerativeModel = + | VertexGenerativeModelPreview + | VertexGenerativeModel + | GoogleGenerativeModel; + +export type ChatContext = { message: Part[]; history: GeminiMessageContent[] }; + +export type GeminiMessageRole = "user" | "model"; + +export type GeminiAdditionalChatOptions = {}; + +export type GeminiChatParamsStreaming = LLMChatParamsStreaming< + GeminiAdditionalChatOptions, + ToolCallLLMMessageOptions +>; + +export type GeminiChatStreamResponse = AsyncIterable< + ChatResponseChunk<ToolCallLLMMessageOptions> +>; + +export type GeminiChatParamsNonStreaming = LLMChatParamsNonStreaming< + GeminiAdditionalChatOptions, + ToolCallLLMMessageOptions +>; + +export type GeminiChatNonStreamResponse = + ChatResponse<ToolCallLLMMessageOptions>; + +export interface IGeminiSession { + getGenerativeModel(metadata: ModelParams): GenerativeModel; + getResponseText( + response: EnhancedGenerateContentResponse | GenerateContentResponse, + ): string; + getCompletionStream( + result: + | GoogleStreamGenerateContentResult + | VertexStreamGenerateContentResult, + ): AsyncIterable<CompletionResponse>; + getChatStream( + result: + | GoogleStreamGenerateContentResult + | VertexStreamGenerateContentResult, + ): GeminiChatStreamResponse; +} diff --git a/packages/core/src/llm/gemini/utils.ts b/packages/core/src/llm/gemini/utils.ts new file mode 100644 index 000000000..fdbd6ef77 --- /dev/null +++ b/packages/core/src/llm/gemini/utils.ts @@ -0,0 +1,197 @@ +import { type Content as GeminiMessageContent } from "@google/generative-ai"; + +import { type GenerateContentResponse } from "@google-cloud/vertexai"; +import type { + ChatMessage, + MessageContent, + MessageContentImageDetail, + MessageContentTextDetail, + MessageType, +} from "../types.js"; +import { extractDataUrlComponents } from "../utils.js"; +import type { + ChatContext, + FileDataPart, + GeminiChatParamsNonStreaming, + GeminiChatParamsStreaming, + GeminiMessageRole, + InlineDataPart, + Part, +} from "./types.js"; + +const FILE_EXT_MIME_TYPES: { [key: string]: string } = { + png: "image/png", + jpeg: "image/jpeg", + jpg: "image/jpeg", + webp: "image/webp", + heic: "image/heic", + heif: "image/heif", +}; +const ACCEPTED_IMAGE_MIME_TYPES = Object.values(FILE_EXT_MIME_TYPES); + +const getFileURLExtension = (url: string): string | null => { + const pathname = new URL(url).pathname; + const parts = pathname.split("."); + return parts.length > 1 ? parts.pop()?.toLowerCase() || null : null; +}; + +const getFileURLMimeType = (url: string): string | null => { + const ext = getFileURLExtension(url); + return ext ? FILE_EXT_MIME_TYPES[ext] || null : null; +}; + +const getImageParts = ( + message: MessageContentImageDetail, +): InlineDataPart | FileDataPart => { + if (message.image_url.url.startsWith("data:")) { + const { mimeType, base64: data } = extractDataUrlComponents( + message.image_url.url, + ); + if (!mimeType || !ACCEPTED_IMAGE_MIME_TYPES.includes(mimeType)) + throw new Error( + `Gemini only accepts the following mimeTypes: ${ACCEPTED_IMAGE_MIME_TYPES.join("\n")}`, + ); + return { + inlineData: { + mimeType, + data, + }, + }; + } + const mimeType = getFileURLMimeType(message.image_url.url); + if (!mimeType || !ACCEPTED_IMAGE_MIME_TYPES.includes(mimeType)) + throw new Error( + `Gemini only accepts the following mimeTypes: ${ACCEPTED_IMAGE_MIME_TYPES.join("\n")}`, + ); + return { + fileData: { mimeType, fileUri: message.image_url.url }, + }; +}; + +export const getPartsText = (parts: Part[]): string => { + const textStrings = []; + if (parts.length) { + for (const part of parts) { + if (part.text) { + textStrings.push(part.text); + } + } + } + if (textStrings.length > 0) { + return textStrings.join(""); + } else { + return ""; + } +}; + +/** + * Returns all text found in all parts of first candidate. + */ +export const getText = (response: GenerateContentResponse): string => { + if (response.candidates?.[0].content?.parts) { + return getPartsText(response.candidates?.[0].content?.parts); + } + return ""; +}; + +export const cleanParts = ( + message: GeminiMessageContent, +): GeminiMessageContent => { + return { + ...message, + parts: message.parts.filter((part) => part.text?.trim()), + }; +}; + +export const getChatContext = ( + params: GeminiChatParamsStreaming | GeminiChatParamsNonStreaming, +): ChatContext => { + // Gemini doesn't allow: + // 1. Consecutive messages from the same role + // 2. Parts that have empty text + const messages = GeminiHelper.mergeNeighboringSameRoleMessages( + params.messages.map(GeminiHelper.chatMessageToGemini), + ).map(cleanParts); + + const history = messages.slice(0, -1); + const message = messages[messages.length - 1].parts; + return { + history, + message, + }; +}; + +/** + * Helper class providing utility functions for Gemini + */ +export class GeminiHelper { + // Gemini only has user and model roles. Put the rest in user role. + public static readonly ROLES_TO_GEMINI: Record< + MessageType, + GeminiMessageRole + > = { + user: "user", + system: "user", + assistant: "model", + memory: "user", + }; + + public static readonly ROLES_FROM_GEMINI: Record< + GeminiMessageRole, + MessageType + > = { + user: "user", + model: "assistant", + }; + + public static mergeNeighboringSameRoleMessages( + messages: GeminiMessageContent[], + ): GeminiMessageContent[] { + return messages.reduce( + ( + result: GeminiMessageContent[], + current: GeminiMessageContent, + index: number, + ) => { + if (index > 0 && messages[index - 1].role === current.role) { + result[result.length - 1].parts = [ + ...result[result.length - 1].parts, + ...current.parts, + ]; + } else { + result.push(current); + } + return result; + }, + [], + ); + } + + public static messageContentToGeminiParts(content: MessageContent): Part[] { + if (typeof content === "string") { + return [{ text: content }]; + } + + const parts: Part[] = []; + const imageContents = content.filter( + (i) => i.type === "image_url", + ) as MessageContentImageDetail[]; + + parts.push(...imageContents.map(getImageParts)); + + const textContents = content.filter( + (i) => i.type === "text", + ) as MessageContentTextDetail[]; + parts.push(...textContents.map((t) => ({ text: t.text }))); + return parts; + } + + public static chatMessageToGemini( + message: ChatMessage, + ): GeminiMessageContent { + return { + role: GeminiHelper.ROLES_TO_GEMINI[message.role], + parts: GeminiHelper.messageContentToGeminiParts(message.content), + }; + } +} diff --git a/packages/core/src/llm/gemini/vertex.ts b/packages/core/src/llm/gemini/vertex.ts new file mode 100644 index 000000000..43c7100da --- /dev/null +++ b/packages/core/src/llm/gemini/vertex.ts @@ -0,0 +1,81 @@ +import { + VertexAI, + GenerativeModel as VertexGenerativeModel, + GenerativeModelPreview as VertexGenerativeModelPreview, + type GenerateContentResponse, + type ModelParams as VertexModelParams, + type StreamGenerateContentResult as VertexStreamGenerateContentResult, +} from "@google-cloud/vertexai"; + +import type { + GeminiChatStreamResponse, + IGeminiSession, + VertexGeminiSessionOptions, +} from "./types.js"; + +import { getEnv } from "@llamaindex/env"; +import type { CompletionResponse } from "../types.js"; +import { streamConverter } from "../utils.js"; +import { getText } from "./utils.js"; + +/* To use Google's Vertex AI backend, it doesn't use api key authentication. + * + * To authenticate for local development: + * + * ``` + * npm install @google-cloud/vertexai + * gcloud auth application-default login + * ``` + * For production the prefered method is via a service account, more + * details: https://cloud.google.com/docs/authentication/ + * + * */ + +export class GeminiVertexSession implements IGeminiSession { + private vertex: VertexAI; + private preview: boolean = false; + + constructor(options?: Partial<VertexGeminiSessionOptions>) { + const project = options?.project ?? getEnv("GOOGLE_VERTEX_PROJECT"); + const location = options?.location ?? getEnv("GOOGLE_VERTEX_LOCATION"); + if (!project || !location) { + throw new Error( + "Set Google Vertex project and location in GOOGLE_VERTEX_PROJECT and GOOGLE_VERTEX_LOCATION env variables", + ); + } + this.vertex = new VertexAI({ + ...options, + project, + location, + }); + this.preview = options?.preview ?? false; + } + + getGenerativeModel( + metadata: VertexModelParams, + ): VertexGenerativeModelPreview | VertexGenerativeModel { + if (this.preview) return this.vertex.preview.getGenerativeModel(metadata); + return this.vertex.getGenerativeModel(metadata); + } + + getResponseText(response: GenerateContentResponse): string { + return getText(response); + } + + async *getChatStream( + result: VertexStreamGenerateContentResult, + ): GeminiChatStreamResponse { + yield* streamConverter(result.stream, (response) => ({ + delta: this.getResponseText(response), + raw: response, + })); + } + getCompletionStream( + result: VertexStreamGenerateContentResult, + ): AsyncIterable<CompletionResponse> { + return streamConverter(result.stream, (response) => ({ + text: this.getResponseText(response), + raw: response, + })); + } +} diff --git a/packages/core/src/llm/index.ts b/packages/core/src/llm/index.ts index 7574dc8ec..6bfc7a63e 100644 --- a/packages/core/src/llm/index.ts +++ b/packages/core/src/llm/index.ts @@ -5,7 +5,13 @@ export { Anthropic, } from "./anthropic.js"; export { FireworksLLM } from "./fireworks.js"; -export { GEMINI_MODEL, Gemini, GeminiSession } from "./gemini.js"; +export { Gemini, GeminiSession } from "./gemini/base.js"; + +export { + GEMINI_MODEL, + type GoogleGeminiSessionOptions, +} from "./gemini/types.js"; + export { Groq } from "./groq.js"; export { HuggingFaceInferenceAPI, HuggingFaceLLM } from "./huggingface.js"; export { diff --git a/packages/core/src/llm/utils.ts b/packages/core/src/llm/utils.ts index 1b22a9c6e..28240fff6 100644 --- a/packages/core/src/llm/utils.ts +++ b/packages/core/src/llm/utils.ts @@ -77,6 +77,27 @@ export function extractText(message: MessageContent): string { } } +export const extractDataUrlComponents = ( + dataUrl: string, +): { + mimeType: string; + base64: string; +} => { + const parts = dataUrl.split(";base64,"); + + if (parts.length !== 2 || !parts[0].startsWith("data:")) { + throw new Error("Invalid data URL"); + } + + const mimeType = parts[0].slice(5); + const base64 = parts[1]; + + return { + mimeType, + base64, + }; +}; + /** * @internal */ diff --git a/packages/core/src/next.ts b/packages/core/src/next.ts index 8097a0ea7..f18e37d5b 100644 --- a/packages/core/src/next.ts +++ b/packages/core/src/next.ts @@ -28,6 +28,7 @@ export default function withLlamaIndex(config: any) { ...webpackConfig.resolve.alias, sharp$: false, "onnxruntime-node$": false, + "@google-cloud/vertexai": false, }; return webpackConfig; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aafc53cb7..5a1c73653 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,7 +157,7 @@ importers: version: link:../packages/core mongodb: specifier: ^6.6.1 - version: 6.6.1 + version: 6.6.1(gcp-metadata@6.1.0(encoding@0.1.13)) pathe: specifier: ^1.1.2 version: 1.1.2 @@ -343,6 +343,9 @@ importers: '@datastax/astra-db-ts': specifier: ^1.1.0 version: 1.1.0 + '@google-cloud/vertexai': + specifier: ^1.1.0 + version: 1.1.0(encoding@0.1.13) '@google/generative-ai': specifier: ^0.11.0 version: 0.11.0 @@ -411,7 +414,7 @@ importers: version: 2.0.0 mongodb: specifier: ^6.6.1 - version: 6.6.1 + version: 6.6.1(gcp-metadata@6.1.0(encoding@0.1.13)) notion-md-crawler: specifier: ^1.0.0 version: 1.0.0(encoding@0.1.13) @@ -2106,6 +2109,10 @@ packages: '@fastify/deepmerge@1.3.0': resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + '@google-cloud/vertexai@1.1.0': + resolution: {integrity: sha512-hfwfdlVpJ+kM6o2b5UFfPnweBcz8tgHAFRswnqUKYqLJsvKU0DDD0Z2/YKoHyAUoPJAv20qg6KlC3msNeUKUiw==} + engines: {node: '>=18.0.0'} + '@google/generative-ai@0.11.0': resolution: {integrity: sha512-nyvrIltnq4RRkUj4FBeDPebWHB1oeALK75aa0xdatEriH05lzBJD6t91afRm5ldtQ5Txu6UwPLuHu5jM1CjtQg==} engines: {node: '>=18.0.0'} @@ -3521,6 +3528,10 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} + agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} @@ -3831,6 +3842,9 @@ packages: big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + bin-check@4.1.0: resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==} engines: {node: '>=4'} @@ -3906,6 +3920,9 @@ packages: resolution: {integrity: sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==} engines: {node: '>=16.20.1'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -4778,6 +4795,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -5406,6 +5426,14 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gaxios@6.5.0: + resolution: {integrity: sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==} + engines: {node: '>=14'} + + gcp-metadata@6.1.0: + resolution: {integrity: sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==} + engines: {node: '>=14'} + generic-pool@3.9.0: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} @@ -5541,6 +5569,10 @@ packages: engines: {node: '>=0.6.0'} hasBin: true + google-auth-library@9.10.0: + resolution: {integrity: sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==} + engines: {node: '>=14'} + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -5568,6 +5600,10 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + guid-typescript@1.0.9: resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} @@ -5763,6 +5799,10 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} + https-proxy-agent@7.0.4: + resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + engines: {node: '>= 14'} + human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -6250,6 +6290,9 @@ packages: engines: {node: '>=4'} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -6304,6 +6347,12 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwa@2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + + jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -9488,6 +9537,10 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + uuidv7@0.6.3: resolution: {integrity: sha512-zV3eW2NlXTsun/aJ7AixxZjH/byQcH/r3J99MI0dDEkU2cJIBJxhEWUHDTpOaLPRNhebPZoeHuykYREkI9HafA==} hasBin: true @@ -12021,6 +12074,13 @@ snapshots: '@fastify/deepmerge@1.3.0': {} + '@google-cloud/vertexai@1.1.0(encoding@0.1.13)': + dependencies: + google-auth-library: 9.10.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - supports-color + '@google/generative-ai@0.11.0': {} '@grpc/grpc-js@1.10.7': @@ -13486,6 +13546,12 @@ snapshots: address@1.2.2: {} + agent-base@7.1.1: + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + agentkeepalive@4.5.0: dependencies: humanize-ms: 1.2.1 @@ -13848,6 +13914,8 @@ snapshots: big.js@5.2.2: {} + bignumber.js@9.1.2: {} + bin-check@4.1.0: dependencies: execa: 0.7.0 @@ -13954,6 +14022,8 @@ snapshots: bson@6.7.0: {} + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -14871,6 +14941,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} electron-to-chromium@1.4.730: {} @@ -15809,6 +15883,25 @@ snapshots: functions-have-names@1.2.3: {} + gaxios@6.5.0(encoding@0.1.13): + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.4 + is-stream: 2.0.1 + node-fetch: 2.7.0(encoding@0.1.13) + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + gcp-metadata@6.1.0(encoding@0.1.13): + dependencies: + gaxios: 6.5.0(encoding@0.1.13) + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + generic-pool@3.9.0: {} gensync@1.0.0-beta.2: {} @@ -15961,6 +16054,18 @@ snapshots: dependencies: minimist: 1.2.8 + google-auth-library@9.10.0(encoding@0.1.13): + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.5.0(encoding@0.1.13) + gcp-metadata: 6.1.0(encoding@0.1.13) + gtoken: 7.1.0(encoding@0.1.13) + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -16008,6 +16113,14 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + gtoken@7.1.0(encoding@0.1.13): + dependencies: + gaxios: 6.5.0(encoding@0.1.13) + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + guid-typescript@1.0.9: {} gzip-size@6.0.0: @@ -16289,6 +16402,13 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + https-proxy-agent@7.0.4: + dependencies: + agent-base: 7.1.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + human-id@1.0.2: {} human-signals@2.1.0: {} @@ -16699,6 +16819,10 @@ snapshots: jsesc@2.5.2: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.1.2 + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} @@ -16759,6 +16883,17 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + jwa@2.0.0: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.0: + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -17658,11 +17793,13 @@ snapshots: '@types/whatwg-url': 11.0.4 whatwg-url: 13.0.0 - mongodb@6.6.1: + mongodb@6.6.1(gcp-metadata@6.1.0(encoding@0.1.13)): dependencies: '@mongodb-js/saslprep': 1.1.6 bson: 6.7.0 mongodb-connection-string-url: 3.0.0 + optionalDependencies: + gcp-metadata: 6.1.0(encoding@0.1.13) mrmime@2.0.0: {} @@ -20574,6 +20711,8 @@ snapshots: uuid@8.3.2: {} + uuid@9.0.1: {} + uuidv7@0.6.3: {} v8-compile-cache-lib@3.0.1: {} -- GitLab