firebase / firebase-functions

Firebase SDK for Cloud Functions
https://firebase.google.com/docs/functions/
MIT License
1.02k stars 201 forks source link

How to pass GOOGLE_GENAI_API_KEY when using `onFlow()` #1586

Closed gregfenton closed 1 month ago

gregfenton commented 1 month ago

Related issues

Possibly #1567

[REQUIRED] Version info

node:

18.20.2

firebase-functions:

5.0.1

firebase-tools:

13.13.2

firebase-admin:

─ firebase-admin@12.2.0 ─ firebase-functions@5.0.1 ─ firebase-functions-test@3.3.0

─ @genkit-ai/firebase@0.5.6 ─ @genkit-ai/flow@0.5.6

[REQUIRED] Test case

Cloud Functions code:

import * as z from "zod";

import {generate} from "@genkit-ai/ai";
import {configureGenkit} from "@genkit-ai/core";
import {firebase} from "@genkit-ai/firebase";
import {googleAI} from "@genkit-ai/googleai";
import {onInit} from "firebase-functions/v2/core";

import {gemini15Flash} from "@genkit-ai/googleai";

import {firebaseAuth} from "@genkit-ai/firebase/auth";
import {onFlow} from "@genkit-ai/firebase/functions";

import {defineSecret} from "firebase-functions/params";
import {onCall} from "firebase-functions/v2/https";
const genAiApiKey = defineSecret("GOOGLE_GENAI_API_KEY");

onInit(() => {
  console.log("GLF: onInit() genAiApiKey:", genAiApiKey?.value());

  configureGenkit({
    plugins: [firebase(), googleAI({apiKey: genAiApiKey.value()})],
    logLevel: "debug",
    enableTracingAndMetrics: true,
  });
});

export const menuSuggestionFlow = onFlow(
  {
    name: "menuSuggestionFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    httpsOptions: {cors: true},
    authPolicy: firebaseAuth((user) => {}),
  },
  async (subject) => {
    const apiKey = process.env.GOOGLE_GENAI_API_KEY; // Access the secret here

    const prompt = `Suggest an item for the menu of a ${subject} themed restaurant`;
    const llmResponse = await generate({
      model: gemini15Flash,
      prompt: prompt,
      config: {
        temperature: 1,
        apiKey: apiKey,
      },
    });

    return llmResponse.text();
  },
);

export const testApiKey = onCall({secrets: ["GOOGLE_GENAI_API_KEY"]}, async () => {
  const apiKey = genAiApiKey.value();
  console.log("API Key:", apiKey);

  return {result: apiKey};
});

[REQUIRED] Steps to reproduce

Follow steps from Deploy a flow as a Cloud Function but replace code in functions/src/index.ts with:

import * as z from "zod";

import {generate} from "@genkit-ai/ai";
import {configureGenkit} from "@genkit-ai/core";
import {firebase} from "@genkit-ai/firebase";
import {googleAI} from "@genkit-ai/googleai";
import {onInit} from "firebase-functions/v2/core";

import {gemini15Flash} from "@genkit-ai/googleai";

import {firebaseAuth} from "@genkit-ai/firebase/auth";
import {onFlow} from "@genkit-ai/firebase/functions";

import {defineSecret} from "firebase-functions/params";
import {onCall} from "firebase-functions/v2/https";
const genAiApiKey = defineSecret("GOOGLE_GENAI_API_KEY");

onInit(() => {
  console.log("GLF: onInit() genAiApiKey:", genAiApiKey?.value());

  configureGenkit({
    plugins: [firebase(), googleAI({apiKey: genAiApiKey.value()})],
    logLevel: "debug",
    enableTracingAndMetrics: true,
  });
});

export const menuSuggestionFlow = onFlow(
  {
    name: "menuSuggestionFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    httpsOptions: {cors: true},
    authPolicy: firebaseAuth((user) => {}),
  },
  async (subject) => {
    const apiKey = process.env.GOOGLE_GENAI_API_KEY; // Access the secret here

    const prompt = `Suggest an item for the menu of a ${subject} themed restaurant`;
    const llmResponse = await generate({
      model: gemini15Flash,
      prompt: prompt,
      config: {
        temperature: 1,
        apiKey: apiKey,
      },
    });

    return llmResponse.text();
  },
);

export const testApiKey = onCall({secrets: ["GOOGLE_GENAI_API_KEY"]}, async () => {
  const apiKey = genAiApiKey.value();
  console.log("API Key:", apiKey);

  return {result: apiKey};
});

[REQUIRED] Expected behavior

Running web client app returns a generated list of restaurant menu items.

[REQUIRED] Actual behavior

Error is logged to browser's JS Console:

Uncaught (in promise) FirebaseError: Please pass in the API key or set the GOOGLE_GENAI_API_KEY or GOOGLE_API_KEY environment variable.
For more details see https://firebase.google.com/docs/genkit/plugins/google-genai

In Logs Explorer I get the error:

Error: Please pass in the API key or set the GOOGLE_GENAI_API_KEY or GOOGLE_API_KEY environment variable.
For more details see https://firebase.google.com/docs/genkit/plugins/qooqle-genai
  at googleAIModel (/workspace/node_modules/@genkit-ai/googleai/lib/gemini.js:393:11)
  at /workspace/node_modules/@genkit-ai/googleai/lib/index.js:76:53
  at Array.map (<anonymous>) 
  at /workspace/node_modules/@genkit-ai/googleai/lib/index.js:75:59
  at Generator.next (<anonvmous> 
  at /workspace/node_modules/@genkit-ai/googleai/lib/index.js:36:6
  at new Promise (<anonymous>)
  at -_async (/workspace/node_modules/@genkit-ai/googleai/lib/index.js:20:18)
  at /workspace/node_modules/@genkit-ai/googleai/lib/index.js:55:16
  at /workspace/node_modules/@genkit-ai/core/lib/plugin.js:48:40

Were you able to successfully deploy your functions?

Yes, they deployed fine. And the function testApiKey when called from the same HTML client runs fine and displays the proper API key value from the Secrets Manager.

google-oss-bot commented 1 month ago

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

chrisraygill commented 1 month ago

@kevinthecheung is this just a docs gap or does it look like a Genkit bug? @mbleigh maybe related to https://github.com/firebase/firebase-functions/issues/1567?

taeold commented 1 month ago

Hi @gregfenton.

It looks like the flow you defined isn't bound to the secret:

export const menuSuggestionFlow = onFlow(
  {
    name: "menuSuggestionFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    httpsOptions: {cors: true},
    authPolicy: firebaseAuth((user) => {}),
    secrets: [genAiApiKey], # LOOK HERE
  },

See the documentation for more info.

It's a common footgun, and I wish there was a more intuitive way to bind secrets to functions. If you have any idea, please let us know.

(FWIW, this issue is unrelated to https://github.com/firebase/firebase-functions/issues/1567)

gregfenton commented 1 month ago

@taeold I added that line to the onFlow() parameters, but that leads to the error:

Object literal may only specify known properties, and 'secrets' does not exist in type 'FunctionFlowConfig<ZodString, ZodString, ZodTypeAny>'.ts(2353)

Seems that TS is telling the truth.

FYI: I use Cloud Functions and Secrets Manager daily.

It is onFlow() that is new to me 😁

Is there reference documentation for onFlow() similar to onCall() ? I can't seem to find it.

taeold commented 1 month ago

@gregfenton Based on https://github.com/firebase/genkit/blob/main/js/plugins/firebase/src/functions.ts#L51, it looks like the correct syntax might be:

export const menuSuggestionFlow = onFlow(
  {
    name: "menuSuggestionFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    httpsOptions: {cors: true, secrets: [genAiApiKey] }, # LOOK HERE
    authPolicy: firebaseAuth((user) => {}), 
  },

Do you mind giving this a try?

Refernce docs would be nice, and I expect them to be incoming!

gregfenton commented 1 month ago

@taeold That totally fixed it! 🥳

Wasn't obvious to me that HttpOptions are actually "HTTP Cloud Function options".

Wait....OMG....was this there the whole time? Please tell me this was the Firebase Genkit team just being super responsive and not that I totally can't read/messed up???!!! 🫣

image

gregfenton commented 1 month ago

image

You all rock!