Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration with Gemeni API #13

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 0 additions & 2 deletions .github/workflows/upload-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ jobs:
echo "GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/service_account.json" >> $GITHUB_ENV
echo ${{ secrets.FIREBASE_SA }} | base64 -d -i - > service_account.json

- run: firebase login:list

- run: yarn --cwd functions install

- run: yarn --cwd functions build
Expand Down
18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
## 0.0.14

- Adding integration with Google Gemini API

## 0.0.13

- Updating node version to 18
- Moving webhooks into secrets instead of Firestore configuration.

## 0.0.7

Fixing function trigger type

## 0.0.1

Initial release.

Implementing support for webhook integration with Google Chat to send a
Implementing support for webhook integration with Google Chat to send a
card with Github and Crashlytics quick actions.

- Refactoring Github repo information in App Info
- Adding crashlytics support for Slack and Discord
- Adding functions to capture billing, app distribution and performance alerts
with no handling apart from debug logging. Needed for collecting sample data.

- Refactoring Github repo information in App Info
- Adding crashlytics support for Slack and Discord
- Adding functions to capture billing, app distribution and performance alerts
with no handling apart from debug logging. Needed for collecting sample data.
10 changes: 0 additions & 10 deletions POSTINSTALL.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
## Configuring your webhooks
Read the official documentation for each of the platforms on how to configure
webhooks.

* [Google Chat](https://developers.google.com/hangouts/chat/how-tos/webhooks)
* [Slack](https://slack.com/help/articles/115005265063-Incoming-webhooks-for-Slack)
* [Discord](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks)

There is a square Firebase icon under the [`/icons/`](https://github.com/oddbit/firebase-alerts/raw/main/icons)
folder that you can use for your webhook avatar. Use this permalink to the image: [https://github.com/oddbit/firebase-alerts/raw/main/icons/firebase.png](https://github.com/oddbit/firebase-alerts/raw/main/icons/firebase.png)


## Configuring apps
Expand Down
51 changes: 25 additions & 26 deletions PREINSTALL.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,57 @@
Use this extension to configure multiple webhooks to social platforms where you
want to receive Firebase Alerts notifications. See the official documentation as
an example use-case: https://firebase.google.com/docs/functions/beta/alert-events#trigger-function-on-alert-events
Use this extension to set up a webhook for social platforms where you want to receive Firebase Alerts notifications. For an example use case, refer to the official documentation: [Firebase Alerts Documentation](https://firebase.google.com/docs/functions/beta/alert-events#trigger-function-on-alert-events).

The social platform notification messages are offering quick actions to jump
straight into the Firebase console for detailed information and optionally to
create github issues if applicable.
This extension enables quick actions through social platform notifications, allowing direct access to the Firebase console for detailed information. Optionally, it supports creating GitHub issues if GitHub repository information is configured.

This extension adds a highly configurable way of registering multiple webhooks to
be triggered for each event. The plugin also supports multiple platforms

- Google Chat
- Slack
- Discord

See [README](https://github.com/oddbit/firebase-alerts#readme) for complete list
of feature and platform support
The extension offers a webhook that are triggered for each event. It also supports multiple platforms. For a complete list of features and supported platforms, see the [README](https://github.com/oddbit/firebase-alerts#readme).

# Configuring the extension

## Webhooks

To install the extension, you must define a webhook for a social platform.
The extension require at least one webhook to be defined during the installation.
At the moment you can only declare one webhook per platform.

This webhook URL can be obtained by reading the apps and integrations documentation
for any of the platforms that are supported by this extension:
[Google Chat](https://developers.google.com/chat/how-tos/webhooks#create_a_webhook),
[Slack](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks),
and [Discord](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks).
for any of the platforms that are supported by this extension:

- [Google Chat](https://developers.google.com/hangouts/chat/how-tos/webhooks)
- [Slack](https://slack.com/help/articles/115005265063-Incoming-webhooks-for-Slack)
- [Discord](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks)

For your webhook avatar, use the square Firebase icon located in the [`/icons/`](https://github.com/oddbit/firebase-alerts/raw/main/icons) folder. Here's a permalink to the image: [Firebase Icon](https://github.com/oddbit/firebase-alerts/raw/main/icons/firebase.png)

## App information

You will be required to explicitly configure app id, bundle in order for the extension
to be able to generate URLs to Firebase console, to make direct links to crashlytics etc.

### App ID
The app ID is the string that is uniquely used by Firebase to identify your application and

The app ID is the string that is uniquely used by Firebase to identify your application and
you can find it in the Firebase console looking something like this: `1:269808624035:android:296863cf1f5b6817c87a16`

### Bundle ID
The bundle id is the ID that you have configured in your mobile app configuration, e.g. `id.oddbit.app.example`.

The bundle id is the ID that you have configured in your mobile app configuration, e.g. `id.oddbit.app.example`.

Although web apps do not have bundle ids, Firebase is still using
an equivalent representation for some of the console URLs. As shown in the example below,
you can find the web app's "bundle ID" on the URL looking something
you can find the web app's "bundle ID" on the URL looking something
like: `web:NzE5YzVlZDktZjJjOS00Y2Y2LTkzNjQtZTM0ZmJhNjU0MmY3`


![Web App Bundle ID](https://github.com/oddbit/firebase-alerts/raw/main/doc/images/web-app-bundle-id.png)

## Integrating Google Gemini API

You can harness the capabilities of Google Gemini's Large Language Model (LLM) by configuring an API key. This will enable the extension to leverage LLM's power to analyze, clarify, and explain each alert in a more insightful and helpful manner.

Read the official documentation on how to retrieve an API key: https://ai.google.dev/tutorials/setup

# Billing

To install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)

- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).
- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s no-cost tier:
- Cloud Functions (Node.js 16+ runtime. [See FAQs](https://firebase.google.com/support/faq#extensions-pricing))
- Cloud Functions (Node.js 16+ runtime. [See FAQs](https://firebase.google.com/support/faq#extensions-pricing))
16 changes: 9 additions & 7 deletions extension.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: firebase-alerts
version: 0.0.13
version: 0.0.14-alpha.4
specVersion: v1beta

displayName: Firebase Alerts
Expand Down Expand Up @@ -115,17 +115,19 @@ params:
required: false
immutable: false

- param: WEBHOOK_MANDATORY
label: Mandatory webhook
description: Mandatory webhook for Slack, Discord, or Google Chat
- param: WEBHOOK_URL
label: Webhook
description: Webhook URL for Slack, Discord, or Google Chat
type: secret
required: true
immutable: false

- param: WEBHOOK_OPTIONAL
label: Optional webhook
description: Optional, additional webhook for Slack, Discord, or Google Chat
- param: API_KEY_GEMINI
label: Google Gemini API key
description: You can create an API key in Google AI Studio.
type: secret
required: false
immutable: false

resources:
- name: anr
Expand Down
1 change: 1 addition & 0 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"main": "lib/src/index.js",
"dependencies": {
"@google/generative-ai": "^0.1.3",
"firebase-admin": "^11.4.1",
"firebase-functions": "^4.1.1",
"request": "^2.88.2"
Expand Down
44 changes: 31 additions & 13 deletions functions/src/alerts/crashlytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {EnvConfig} from "../utils/env-config";
import {DiscordWebhook} from "../webhook-plugins/discord";
import {GoogleChatWebhook} from "../webhook-plugins/google-chat";
import {SlackWebhook} from "../webhook-plugins/slack";
import {GeminiService} from "../services/gemini.service";

const functionOpts = {
region: process.env.LOCATION,
secrets: ["WEBHOOK_MANDATORY", "WEBHOOK_OPTIONAL"],
secrets: ["WEBHOOK_URL", "API_KEY_GEMINI"],
};

/**
Expand Down Expand Up @@ -54,14 +55,8 @@ async function handleCrashlyticsEvent(appCrash: AppCrash):
return;
}

const webhooks: Webhook[] = EnvConfig.webhooks.map(webhookPluginFromUrl);

if (webhooks.length === 0) {
throw new Error("No webhooks defined. Please reconfigure the extension!");
}

const promises = [];
for (const webhook of webhooks) {
for (const webhook of EnvConfig.webhooks.map(webhookPluginFromUrl)) {
logger.debug("[handleCrashlyticsEvent] Webhook", webhook);
const crashlyticsMessage = webhook.createCrashlyticsMessage(appCrash);
const webhookPayload = {
Expand Down Expand Up @@ -90,40 +85,63 @@ async function handleCrashlyticsEvent(appCrash: AppCrash):
export const anr =
crashlytics.onNewAnrIssuePublished(functionOpts, async (event) => {
logger.debug("onNewAnrIssuePublished", event);

const appCrash = AppCrash.fromCrashlytics(event);
if (EnvConfig.apiKeyGemini) {
logger.debug("Call Gemini API for explanation");
const geminiService = new GeminiService(EnvConfig.apiKeyGemini);
appCrash.explanation = await geminiService.explainCrash(event);
logger.debug("Gemini explanation", appCrash.explanation);
}

appCrash.tags.push("critical");

return handleCrashlyticsEvent(appCrash);
});

export const fatal =
crashlytics.onNewFatalIssuePublished(functionOpts, (event) => {
crashlytics.onNewFatalIssuePublished(functionOpts, async (event) => {
logger.debug("onNewFatalIssuePublished", event);

const appCrash = AppCrash.fromCrashlytics(event);

if (EnvConfig.apiKeyGemini) {
logger.debug("Call Gemini API for explanation");
const geminiService = new GeminiService(EnvConfig.apiKeyGemini);
appCrash.explanation = await geminiService.explainCrash(event);
logger.debug("Gemini explanation", appCrash.explanation);
}
appCrash.tags.push("critical");

return handleCrashlyticsEvent(appCrash);
});


export const nonfatal =
crashlytics.onNewNonfatalIssuePublished(functionOpts, (event) => {
crashlytics.onNewNonfatalIssuePublished(functionOpts, async (event) => {
logger.debug("onNewNonfatalIssuePublished", event);

const appCrash = AppCrash.fromCrashlytics(event);
if (EnvConfig.apiKeyGemini) {
logger.debug("Call Gemini API for explanation");
const geminiService = new GeminiService(EnvConfig.apiKeyGemini);
appCrash.explanation = await geminiService.explainCrash(event);
logger.debug("Gemini explanation", appCrash.explanation);
}

return handleCrashlyticsEvent(appCrash);
});

export const regression =
crashlytics.onRegressionAlertPublished(functionOpts, (event) => {
crashlytics.onRegressionAlertPublished(functionOpts, async (event) => {
logger.debug("onRegressionAlertPublished", event);

const appCrash = AppCrash.fromCrashlytics(event);
if (EnvConfig.apiKeyGemini) {
logger.debug("Call Gemini API for explanation");
const geminiService = new GeminiService(EnvConfig.apiKeyGemini);
appCrash.explanation = await geminiService.explainCrash(event);
logger.debug("Gemini explanation", appCrash.explanation);
}

appCrash.tags.push("regression");

Expand Down
5 changes: 5 additions & 0 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
// import * as billingFunctions from "./alerts/billing";
// import * as appDistributionFunctions from "./alerts/app-distribution";

import * as admin from "firebase-admin";

admin.initializeApp();


export * from "./alerts/crashlytics";

// TODO: Uncomment when functions are supported. Keeping them commented out now
Expand Down
4 changes: 3 additions & 1 deletion functions/src/models/app-crash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class AppCrash implements IAppCrash {
* @return {AppCrash} An instance of `AppCrash` class
*/
public static fromCrashlytics(event: SupportedCrashlyticsEvent): AppCrash {

const appCrash = {
issueId: event.data.payload.issue.id,
issueTitle: event.data.payload.issue.title,
Expand Down Expand Up @@ -83,5 +84,6 @@ export class AppCrash implements IAppCrash {
public readonly issueTitle: string;
public readonly appId: string;
public readonly appVersion: string;
public readonly tags = ["bug"];
public readonly tags = ["bug"];
public explanation?: string;
}
69 changes: 69 additions & 0 deletions functions/src/services/gemini.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
GoogleGenerativeAI,
} from "@google/generative-ai";
import { SupportedCrashlyticsEvent } from "../models/app-crash";

/**
* Implements a service for interacting with Gemini LLM
*/
export class GeminiService {
constructor(apiKey: string) {
this.genAI = new GoogleGenerativeAI(apiKey);
}

private genAI: GoogleGenerativeAI;
private static readonly MODEL_NAME = "gemini-pro";
private static readonly GENERATION_CONFIG = {
temperature: 0.9,
topK: 1,
topP: 1,
maxOutputTokens: 2048,
};

// Block harmful content, hate speech etc
// Should not be needed or applicable to this use case
private static readonly SAFETY_SETTINGS = [];

/**
* Make Gemini LLM explain a crashlytics event.
*
* @param {SupportedCrashlyticsEvent} appCrash App crash information
* @returns {Promise<string>} Promise with the explanation
*/
async explainCrash(appCrash: SupportedCrashlyticsEvent): Promise<string> {
const model = this.genAI.getGenerativeModel({ model: GeminiService.MODEL_NAME });

const promptContext = `
@type defines the type of crashlytics issue.

CrashlyticsRegressionAlertPayload is an issue that was previously fixed but has reappeared.
resolveTime - The time that the Crashlytics issues was most recently resolved before it began to reoccur.

CrashlyticsNewNonfatalIssuePayload is a Non Fatal issue.

CrashlyticsNewFatalIssuePayload is a Fatal issue.

CrashlyticsNewAnrIssuePayload is an Application Not Responding issue.
`;

const promptExplanation = `
Explain the the following crashlytics issue for a software developer.
Explain the issue in a way that is easy to understand and actionable.
Use the following JSON information to explain the issue:
`;

const crashJson = JSON.stringify(appCrash)

const parts = [
{text: [promptContext, promptExplanation, crashJson].join("\n\n")},
];

const result = await model.generateContent({
contents: [{ role: "user", parts }],
generationConfig: GeminiService.GENERATION_CONFIG,
safetySettings: GeminiService.SAFETY_SETTINGS,
});

return result.response.text();
}
}
10 changes: 8 additions & 2 deletions functions/src/utils/env-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,17 @@ export class EnvConfig {
*/
static get webhooks(): string[] {
return [
process.env.WEBHOOK_MANDATORY,
process.env.WEBHOOK_OPTIONAL,
process.env.WEBHOOK_URL,
].filter((x) => !!x) as string[];
}

/**
* Get Google Gemini API key
*/
static get apiKeyGemini(): string | undefined {
return process.env.API_KEY_GEMINI;
}


/**
* Get an environment variable's value
Expand Down
Loading