Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kits/firestore-vector-search/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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-<instance id>-` 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
Expand Down
18 changes: 8 additions & 10 deletions kits/firestore-vector-search/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
4 changes: 0 additions & 4 deletions kits/firestore-vector-search/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
expr,
projectID,
select,
storageBucket,
} from "firebase-functions/params";
import type { VectorSearchConfig } from "./export-config";

Expand All @@ -48,7 +47,6 @@ const instanceId = defineString("INSTANCE_ID");

const EMBEDDING_PROVIDER_OPTIONS = [
"gemini",
"multimodal",
"openai",
"vertex",
"custom",
Expand All @@ -68,7 +66,6 @@ const params = {
default: "gemini",
input: select({
Gemini: "gemini",
Multimodal: "multimodal",
OpenAI: "openai",
"Vertex AI": "vertex",
"Other (User-provided endpoint)": "custom",
Expand Down Expand Up @@ -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(),
Expand Down

This file was deleted.

3 changes: 0 additions & 3 deletions kits/firestore-vector-search/src/embeddings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -34,7 +33,5 @@ export function createEmbedClient(
return new OpenAiEmbedClient(config);
case "custom":
return new CustomEndpointClient(config);
case "multimodal":
return new MultimodalEmbedClient(config);
}
}
28 changes: 16 additions & 12 deletions kits/firestore-vector-search/src/export-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -47,7 +49,6 @@ export interface VectorSearchConfig {
updateOnConfigure?: boolean;
region?: string;
projectId: string;
bucketName?: string;
instanceId: string;
queueNames?: Partial<QueueNames>;
}
Expand All @@ -69,7 +70,6 @@ export interface ResolvedVectorSearchConfig {
updateOnConfigure: boolean;
region?: string;
projectId: string;
bucketName: string;
instanceId: string;
queueNames: QueueNames;
dimension: number;
Expand Down Expand Up @@ -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":
Expand All @@ -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(", ")}.`
);
}
Comment on lines +130 to +135

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In strict TypeScript configurations, checking !EMBEDDING_PROVIDERS.includes(embeddingProvider) when embeddingProvider is already typed as EmbeddingProvider can trigger a linter or compiler warning (such as no-unnecessary-condition) because the condition is statically determined to be always false. Casting EMBEDDING_PROVIDERS to readonly string[] avoids this issue while preserving the runtime validation.

Suggested change
if (!EMBEDDING_PROVIDERS.includes(embeddingProvider)) {
throw new Error(
`Unsupported embedding provider "${embeddingProvider}". ` +
`Supported providers: ${EMBEDDING_PROVIDERS.join(", ")}.`
);
}
if (!(EMBEDDING_PROVIDERS as readonly string[]).includes(embeddingProvider)) {
throw new Error(
"Unsupported embedding provider \"" + embeddingProvider + "\". " +
"Supported providers: " + EMBEDDING_PROVIDERS.join(", ") + "."
);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declining. This kit runs no eslint (no config or dependency), so no-unnecessary-condition cannot fire, and tsc compiles Array.prototype.includes on the readonly tuple with a union-typed argument without complaint - CI is green on this code. The guard exists because embeddingProvider reaches here through casts from env strings, so the runtime check is not statically dead; widening the array to readonly string[] would only loosen the types the guard relies on.

return {
embeddingProvider: config.embeddingProvider ?? "gemini",
embeddingProvider,
geminiApiKey: config.geminiApiKey,
openAiApiKey: config.openAiApiKey,
customEmbeddingsEndpoint: config.customEmbeddingsEndpoint,
Expand All @@ -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),
Expand Down
5 changes: 0 additions & 5 deletions kits/firestore-vector-search/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const TASK_MAX_ATTEMPTS = 50;
const REQUIRED_ROLES: ReadonlyArray<Role> = [
"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",
Expand All @@ -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
Expand Down
70 changes: 70 additions & 0 deletions kits/firestore-vector-search/tests/config.test.ts
Original file line number Diff line number Diff line change
@@ -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<EmbeddingProvider> = [
"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);
}
});
});
Loading