firestore-vector-search/functions/src/index.ts (123 lines of code) (raw):

/** * Copyright 2023 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 {config} from './config'; import * as functions from 'firebase-functions'; import { FirestoreBackfillOptions, firestoreProcessBackfillTask, firestoreProcessBackfillTrigger, } from '@invertase/firebase-extension-utilities'; import {handleQueryCall} from './queries/query_on_call'; import { embedProcess, handleEmbedOnWrite, updateEmbedProcess, } from './embeddings'; import {handleQueryOnWrite} from './queries'; import {DocumentData} from '@google-cloud/firestore'; import {getExtensions} from 'firebase-admin/extensions'; import * as logs from './logs'; import {createIndex} from './queries/setup'; logs.init(); // Embeddings const runtime = getExtensions().runtime(); export const embedOnWrite = functions.firestore .document(`${config.collectionName}/{docId}`) .onWrite(handleEmbedOnWrite); const shouldDoBackfill = async (metadata?: DocumentData) => { if (!config.doBackfill) { await runtime.setProcessingState( 'PROCESSING_WARNING', `Backfill is disabled, index setup will start with the first write operation to the collection ${config.collectionName}.` ); return false; } // if the embedding provider or model is different, run the backfill if ( !metadata || metadata.embeddingProvider !== config.embeddingProvider || metadata.dimension !== config.dimension || metadata.inputField !== config.inputField || metadata.outputField !== config.outputField ) { await runtime.setProcessingState( 'NONE', `Updating Embeddings for collection ${config.collectionName}.` ); return true; } return false; }; const backfillOptions: FirestoreBackfillOptions = { queueName: config.backfillQueueName, collectionName: config.collectionName, metadataDocumentPath: config.indexMetadataDocumentPath, shouldDoBackfill, statusField: config.statusField, extensionInstanceId: config.instanceId, metadata: { embeddingProvider: config.embeddingProvider, dimension: config.dimension, inputField: config.inputField, outputField: config.outputField, }, }; // Backfill const onInstallTrigger = firestoreProcessBackfillTrigger( embedProcess, backfillOptions ); export const backfillTrigger = functions.tasks .taskQueue() .onDispatch(async (_data, _context) => { try { await createIndex({ collectionName: config.collectionName, dimension: config.dimension, projectId: config.projectId, fieldPath: config.outputField, }); } catch (e) { console.error(e); throw new Error('Error creating firestore Vector index'); } return onInstallTrigger(); }); export const backfillTask = functions.tasks .taskQueue() .onDispatch(firestoreProcessBackfillTask(embedProcess, backfillOptions)); // Update onConfigure const updateOptions: FirestoreBackfillOptions = { queueName: config.updateQueueName, collectionName: config.collectionName, statusField: config.statusField, metadataDocumentPath: config.indexMetadataDocumentPath, shouldDoBackfill, extensionInstanceId: config.instanceId, metadata: { embeddingProvider: config.embeddingProvider, }, }; const onUpdateTrigger = firestoreProcessBackfillTrigger( updateEmbedProcess, updateOptions ); export const updateTrigger = functions.tasks .taskQueue() .onDispatch(async (_data, _context) => { try { await createIndex({ collectionName: config.collectionName, dimension: config.dimension, projectId: config.projectId, fieldPath: config.outputField, }); } catch (e) { console.error(e); throw new Error( 'Error creating new firestore Vector index during update' ); } return onUpdateTrigger(); }); export const updateTask = functions.tasks .taskQueue() .onDispatch(firestoreProcessBackfillTask(updateEmbedProcess, updateOptions)); // Queries export const queryOnWrite = functions.firestore .document(`${config.queryCollectionName}/{queryId}`) .onWrite(handleQueryOnWrite); export const queryCallable = functions.https.onCall(handleQueryCall);