diff --git a/kits/firestore-vector-search/CHANGELOG.md b/kits/firestore-vector-search/CHANGELOG.md index bccb4a65f6..f2a6da7ef3 100644 --- a/kits/firestore-vector-search/CHANGELOG.md +++ b/kits/firestore-vector-search/CHANGELOG.md @@ -1,3 +1,4 @@ +- Removed the unimplemented `multimodal` embedding provider: `EMBEDDING_PROVIDER` no longer offers the value, and config resolution rejects it instead of deploying functions that fail on every embedding. The multimodal-only Cloud Storage requirements (`storage-component.googleapis.com` API, `roles/storage.objectAdmin` role) were dropped with it, and the unused `bucketName` field was removed from the exported `VectorSearchConfig` and `ResolvedVectorSearchConfig` types. On an existing install still set to `multimodal`, every function throws `Unsupported EMBEDDING_PROVIDER "multimodal"` on its first invocation after upgrading, starting with the init task; set `EMBEDDING_PROVIDER` to a supported value and redeploy. - Fixed the backfill and update task dispatch failing with "Queue does not exist": the kit prefixed queue names with `kit--` itself, which the Admin SDK then prefixed again from `FIREBASE_KIT_INSTANCE_ID`. The four `*_QUEUE_NAME` settings now take the deployed function name without that prefix - OpenAI embeddings are back on the extension's model and size: `EMBEDDING_PROVIDER: openai` requests `text-embedding-ada-002` at its native 1536 dimensions with a batch size of 16, replacing `text-embedding-3-small` pinned at 512 with a batch size of 1. Vectors written by an earlier version of the kit are not comparable with the ones it writes now, so re-embed the collection after upgrading. The vector index the kit creates for OpenAI is still declared with 512 dimensions, exactly as the extension declared it, so it does not cover the 1536-dimension vectors and `findNearest` fails against it; create the 1536-dimension index yourself if you query an OpenAI-embedded collection. - Initial release of kit, see README for differences between the legacy extension and this kit diff --git a/kits/firestore-vector-search/README.md b/kits/firestore-vector-search/README.md index 59806d10d4..3217831f3b 100644 --- a/kits/firestore-vector-search/README.md +++ b/kits/firestore-vector-search/README.md @@ -27,12 +27,10 @@ conflicts with that automatic setup. |---|---| | `roles/datastore.user` | read/write documents and embeddings | | `roles/aiplatform.user` | Vertex AI embeddings when configured | -| `roles/storage.objectAdmin` | read image inputs from Cloud Storage | | `roles/datastore.indexAdmin` | manage vector indexes | | `roles/eventarc.eventReceiver` | receive Gen2 Firestore trigger events | | `roles/run.invoker` | allow Eventarc/Tasks to invoke the Gen2 Cloud Run service | | `aiplatform.googleapis.com` | Vertex AI embedding/search | -| `storage-component.googleapis.com` | read image data from Cloud Storage | ## Usage @@ -150,16 +148,16 @@ This kit is version 0.1.3 of the extension repackaged as an npm package, and it the least literal of the ports. The seven functions, the Firestore vector index, the query document collection and the callable all survive with their names and settings intact, so a `.env` copied from your installed instance needs no value -changes. Multimodal embedding, the backfill, and the shape of the status field -written onto your documents all changed, so read this before you point the kit at -a collection an installed instance has already embedded. +changes. Multimodal embedding is gone, and the backfill and the shape of the +status field written onto your documents changed, so read this before you point +the kit at a collection an installed instance has already embedded. -### `EMBEDDING_PROVIDER: multimodal` is not implemented +### `EMBEDDING_PROVIDER: multimodal` is not available -Selecting `multimodal` deploys, and then every embedding attempt throws -`Multimodal embeddings are not implemented in this package`. The extension's -multimodal image embedding, including reading images out of Cloud Storage, has no -equivalent here. If you use it, stay on the extension. +The extension's multimodal image embedding, including reading images out of Cloud +Storage, has no equivalent here. The kit does not offer the `multimodal` value, +and config resolution rejects it with `Unsupported EMBEDDING_PROVIDER +"multimodal"`. If you use it, stay on the extension. ### Gemini and Vertex AI embeddings are truncated to 768 dimensions diff --git a/kits/firestore-vector-search/src/config.ts b/kits/firestore-vector-search/src/config.ts index 8b1075c863..04680e2466 100644 --- a/kits/firestore-vector-search/src/config.ts +++ b/kits/firestore-vector-search/src/config.ts @@ -23,7 +23,6 @@ import { expr, projectID, select, - storageBucket, } from "firebase-functions/params"; import type { VectorSearchConfig } from "./export-config"; @@ -48,7 +47,6 @@ const instanceId = defineString("INSTANCE_ID"); const EMBEDDING_PROVIDER_OPTIONS = [ "gemini", - "multimodal", "openai", "vertex", "custom", @@ -68,7 +66,6 @@ const params = { default: "gemini", input: select({ Gemini: "gemini", - Multimodal: "multimodal", OpenAI: "openai", "Vertex AI": "vertex", "Other (User-provided endpoint)": "custom", @@ -226,7 +223,6 @@ export function configFromEnv(): VectorSearchConfig { instanceId: params.instanceId.value(), geminiApiKey: optionalString(geminiApiKey.value()), openAiApiKey: optionalString(openAiApiKey.value()), - bucketName: optionalString(storageBucket.value()), queueNames: { updateTrigger: params.updateTriggerQueueName.value(), updateTask: params.updateTaskQueueName.value(), diff --git a/kits/firestore-vector-search/src/embeddings/client/multimodal/index.ts b/kits/firestore-vector-search/src/embeddings/client/multimodal/index.ts deleted file mode 100644 index f81a7e9e1a..0000000000 --- a/kits/firestore-vector-search/src/embeddings/client/multimodal/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2026 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ResolvedVectorSearchConfig } from "../../../export-config"; -import { BaseEmbedClient } from "../base_class"; - -export class MultimodalEmbedClient extends BaseEmbedClient { - constructor(_config: ResolvedVectorSearchConfig) { - super(1); - } - - async getEmbeddings(_inputs: ReadonlyArray): Promise { - throw new Error( - "Multimodal embeddings are not implemented in this package" - ); - } -} diff --git a/kits/firestore-vector-search/src/embeddings/index.ts b/kits/firestore-vector-search/src/embeddings/index.ts index d84011516d..0ea0ef0101 100644 --- a/kits/firestore-vector-search/src/embeddings/index.ts +++ b/kits/firestore-vector-search/src/embeddings/index.ts @@ -17,7 +17,6 @@ import type { ResolvedVectorSearchConfig } from "../export-config"; import type { EmbedClient } from "./client/base_class"; import { GenkitEmbedClient } from "./client/genkit"; -import { MultimodalEmbedClient } from "./client/multimodal"; import { CustomEndpointClient } from "./client/text/custom_function"; import { OpenAiEmbedClient } from "./client/text/open_ai"; @@ -34,7 +33,5 @@ export function createEmbedClient( return new OpenAiEmbedClient(config); case "custom": return new CustomEndpointClient(config); - case "multimodal": - return new MultimodalEmbedClient(config); } } diff --git a/kits/firestore-vector-search/src/export-config.ts b/kits/firestore-vector-search/src/export-config.ts index 8b34ead07f..747df0f55b 100644 --- a/kits/firestore-vector-search/src/export-config.ts +++ b/kits/firestore-vector-search/src/export-config.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -export type EmbeddingProvider = - | "gemini" - | "multimodal" - | "openai" - | "vertex" - | "custom"; +export const EMBEDDING_PROVIDERS = [ + "gemini", + "openai", + "vertex", + "custom", +] as const; + +export type EmbeddingProvider = (typeof EMBEDDING_PROVIDERS)[number]; export type DistanceMeasure = "COSINE" | "EUCLIDEAN" | "DOT_PRODUCT"; @@ -47,7 +49,6 @@ export interface VectorSearchConfig { updateOnConfigure?: boolean; region?: string; projectId: string; - bucketName?: string; instanceId: string; queueNames?: Partial; } @@ -69,7 +70,6 @@ export interface ResolvedVectorSearchConfig { updateOnConfigure: boolean; region?: string; projectId: string; - bucketName: string; instanceId: string; queueNames: QueueNames; dimension: number; @@ -100,8 +100,6 @@ function dimensionFor(config: VectorSearchConfig): number { case "gemini": case "vertex": return 768; - case "multimodal": - return 1408; case "openai": return 512; case "custom": @@ -128,8 +126,15 @@ export function resolveVectorSearchConfig( ): ResolvedVectorSearchConfig { const instanceId = config.instanceId; const projectId = config.projectId; + const embeddingProvider = config.embeddingProvider ?? "gemini"; + if (!EMBEDDING_PROVIDERS.includes(embeddingProvider)) { + throw new Error( + `Unsupported EMBEDDING_PROVIDER "${embeddingProvider}". ` + + `Set it to one of: ${EMBEDDING_PROVIDERS.join(", ")}.` + ); + } return { - embeddingProvider: config.embeddingProvider ?? "gemini", + embeddingProvider, geminiApiKey: config.geminiApiKey, openAiApiKey: config.openAiApiKey, customEmbeddingsEndpoint: config.customEmbeddingsEndpoint, @@ -145,7 +150,6 @@ export function resolveVectorSearchConfig( updateOnConfigure: config.updateOnConfigure ?? false, region: config.region ?? process.env.FUNCTION_REGION, projectId, - bucketName: config.bucketName ?? `${projectId}.appspot.com`, instanceId, queueNames: resolveQueueNames(config.queueNames), dimension: dimensionFor(config), diff --git a/kits/firestore-vector-search/src/index.ts b/kits/firestore-vector-search/src/index.ts index aa4286f42f..9c9ff41196 100644 --- a/kits/firestore-vector-search/src/index.ts +++ b/kits/firestore-vector-search/src/index.ts @@ -57,7 +57,6 @@ const TASK_MAX_ATTEMPTS = 50; const REQUIRED_ROLES: ReadonlyArray = [ "roles/datastore.user", "roles/aiplatform.user", - "roles/storage.objectAdmin", "roles/datastore.indexAdmin", // Lifecycle setup enqueues the init task, which may enqueue backfill/update tasks. "roles/cloudtasks.enqueuer", @@ -77,10 +76,6 @@ const REQUIRED_APIS = [ reason: "This extension uses Vertex AI for embedding and vector search when configured.", }, - { - api: "storage-component.googleapis.com", - reason: "Needed to read image data from Cloud Storage.", - }, ] as const; const FUNCTION_SECRETS = [geminiApiKey, openAiApiKey]; // Only the task functions reach getSingleEmbedding, but every function here diff --git a/kits/firestore-vector-search/tests/config.test.ts b/kits/firestore-vector-search/tests/config.test.ts new file mode 100644 index 0000000000..bd86728c5a --- /dev/null +++ b/kits/firestore-vector-search/tests/config.test.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { declaredParams } from "firebase-functions/params"; +import { describe, expect, test } from "vitest"; + +import "../src/config"; +import { + type EmbeddingProvider, + resolveVectorSearchConfig, +} from "../src/export-config"; + +describe("EMBEDDING_PROVIDER", () => { + test("the provider select does not offer multimodal", () => { + const param = declaredParams.find((p) => p.name === "EMBEDDING_PROVIDER"); + expect(param).toBeDefined(); + + const input = param?.options.input; + if (!input || !("select" in input)) { + throw new Error("EMBEDDING_PROVIDER must be a select input"); + } + + const values = input.select.options.map((option) => option.value); + expect(values).toEqual(["gemini", "openai", "vertex", "custom"]); + }); + + test("resolving a config with the removed multimodal provider throws", () => { + expect(() => + resolveVectorSearchConfig({ + projectId: "demo-project", + instanceId: "test", + embeddingProvider: "multimodal" as unknown as EmbeddingProvider, + }) + ).toThrow( + 'Unsupported EMBEDDING_PROVIDER "multimodal". Set it to one of: gemini, openai, vertex, custom.' + ); + }); + + test("every offered provider resolves", () => { + const providers: ReadonlyArray = [ + "gemini", + "openai", + "vertex", + "custom", + ]; + for (const embeddingProvider of providers) { + const config = resolveVectorSearchConfig({ + projectId: "demo-project", + instanceId: "test", + embeddingProvider, + customEmbeddingsDimension: 256, + }); + expect(config.embeddingProvider).toBe(embeddingProvider); + expect(config.dimension).toBeGreaterThan(0); + } + }); +});