Skip to content

feat(firebase-ai): create ai package, vertexai wraps around it #8555

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

Open
wants to merge 84 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
c17a2c9
chore: initial cp over to ai directory
russellwheatley Jun 2, 2025
06a24a7
refactor: move to AI implementation, including AIModel base class
russellwheatley Jun 3, 2025
44183d6
chore: update types for FirebaseApp to match JS SDK
russellwheatley Jun 3, 2025
fd0df2b
chore: update CHANGELOG & package.json
russellwheatley Jun 3, 2025
649eaf5
format
russellwheatley Jun 3, 2025
547ce8a
request-helpers
russellwheatley Jun 3, 2025
ea4c130
request.ts
russellwheatley Jun 3, 2025
bc0ab3b
response-helpers.ts
russellwheatley Jun 3, 2025
86ddd94
schema-builder.ts
russellwheatley Jun 3, 2025
68f6669
stream-reader.ts
russellwheatley Jun 3, 2025
dc3cacb
chat-session-helpers.ts
russellwheatley Jun 3, 2025
0582d8f
chat-session.ts
russellwheatley Jun 3, 2025
d65fe3d
count-tokens.ts
russellwheatley Jun 3, 2025
70bf812
generate-content.ts
russellwheatley Jun 3, 2025
fc7c77e
models index
russellwheatley Jun 3, 2025
64ae763
enums.ts
russellwheatley Jun 3, 2025
3047eb1
error.ts
russellwheatley Jun 3, 2025
2febfdc
chore: add googleai to exports
russellwheatley Jun 3, 2025
68421ef
request.ts types
russellwheatley Jun 3, 2025
e8af3fc
types/responses.ts
russellwheatley Jun 3, 2025
1c24dd9
types/schema.ts
russellwheatley Jun 3, 2025
e28877e
test: backend
russellwheatley Jun 3, 2025
ba719a5
add license header to test file
russellwheatley Jun 3, 2025
cfc0bc8
test: googleai-mapper
russellwheatley Jun 4, 2025
d275f19
chore: firebase_ai yarn.lock
russellwheatley Jun 4, 2025
38c1799
test(ai): update unit test for firebase ai
russellwheatley Jun 4, 2025
b56cd78
test: update convert mocks in line with latest mock response repo
russellwheatley Jun 4, 2025
25dbed0
test(ai): update script name to run
russellwheatley Jun 4, 2025
cb61e8c
test(ai): getMockResponse()
russellwheatley Jun 4, 2025
b5c61dc
test(ai): mock-response update for new mocks
russellwheatley Jun 4, 2025
214c235
test(ai): count tokens unit tests
russellwheatley Jun 4, 2025
cf2bbd7
test: generate-content mock response
russellwheatley Jun 4, 2025
10d081d
test(ai): update unit tests to use updated mocks
russellwheatley Jun 4, 2025
6541dee
test: fix unit tests
russellwheatley Jun 4, 2025
4102574
test: fixed another unit test suite
russellwheatley Jun 4, 2025
6fb7bda
test: add TS no-check to generated mocks
russellwheatley Jun 4, 2025
f210ac4
refactor(vertexai): initial setup to use ai package
russellwheatley Jun 5, 2025
01f9da0
refactor: remove vertexai code and wrap around firebase ai package
russellwheatley Jun 5, 2025
680e9a7
chore: update script name
russellwheatley Jun 5, 2025
f26b73b
chore: firebaseerror from utils
russellwheatley Jun 5, 2025
4bd631b
chore: import vertex constant
russellwheatley Jun 5, 2025
daa488f
chore(vertexai): rm testing from vertex package
russellwheatley Jun 5, 2025
96de44a
chore: update logger to ai
russellwheatley Jun 5, 2025
1ef56fb
chore(vertexai): rm obsolete files
russellwheatley Jun 5, 2025
a285a7c
chore(vertexai): revert back to prev. tsconfig
russellwheatley Jun 5, 2025
70fb2fe
chore(vertexai): symlink ai lib/ folder
russellwheatley Jun 5, 2025
e520bf1
chore(vertexai): wrap around ai
russellwheatley Jun 5, 2025
f4161fc
chore(vertexai): remove logger
russellwheatley Jun 5, 2025
5a8382f
test(vertexai): rm e2e tests
russellwheatley Jun 5, 2025
c307142
refactor: make ai dependency on vertexai
russellwheatley Jun 6, 2025
53ba822
test: fix test
russellwheatley Jun 6, 2025
ac4a1ab
fix: do not use RN URL to construct url
russellwheatley Jun 6, 2025
202bbcb
chore(ai): write example app for ai package
russellwheatley Jun 6, 2025
390832b
fix(ai): allow auth and app check to be passed in
russellwheatley Jun 6, 2025
4e69b4e
chore: clean up of exports
russellwheatley Jun 6, 2025
9c43695
chore: rm mock script from vertex
russellwheatley Jun 6, 2025
159fbc3
chore: update linter ignore list
russellwheatley Jun 6, 2025
4315a5f
docs(ai): write usage docs
russellwheatley Jun 6, 2025
87e3968
format
russellwheatley Jun 6, 2025
cc698e7
rm note
russellwheatley Jun 6, 2025
2f346ff
scrub TODO
russellwheatley Jun 6, 2025
c76a840
test: ensure all types are exported
russellwheatley Jun 24, 2025
99d6439
chore: pr feedback
russellwheatley Jul 1, 2025
203cfd6
chore: rm code comment
russellwheatley Jul 1, 2025
4fefc5a
Apply suggestions from code review
russellwheatley Jul 18, 2025
778fa21
chore: finish string
russellwheatley Jul 21, 2025
83bc393
fix(storage, other): work around missing TextEncoder implementation
mikehardy Jul 22, 2025
d25ce24
test(ai): add example app to list of example apps
mikehardy Aug 5, 2025
d9b29ed
Update packages/app/lib/modular/index.js
russellwheatley Aug 6, 2025
860a8d6
docs(ai): update CHANGELOG with code change
russellwheatley Aug 11, 2025
9ef763f
chore(ai): license header date
russellwheatley Aug 11, 2025
94281e8
chore(ai): license headers
russellwheatley Aug 11, 2025
c22c804
chore(ai): deprecate `totalBillableCharacters`
russellwheatley Aug 11, 2025
e59686e
test(ai): update to VertexAI backend
russellwheatley Aug 11, 2025
39bed82
test: create tests for googlebackend as well
russellwheatley Aug 11, 2025
424cd8e
test: fix test
russellwheatley Aug 11, 2025
f024c97
chore(ai): license headers
russellwheatley Aug 12, 2025
6f7515b
test(ai): used correct type
russellwheatley Aug 12, 2025
eb820bb
docs(ai): mapPromptFeedback() inline documentation
russellwheatley Aug 12, 2025
4be82b3
docs(ai): inline documentation for `countToken()`
russellwheatley Aug 12, 2025
8a38479
docs(ai): generate-content.ts inline docs
russellwheatley Aug 12, 2025
389166e
docs(ai): inline docs for enums.ts
russellwheatley Aug 12, 2025
1089a61
docs(ai): license header date
russellwheatley Aug 12, 2025
961cf50
test(ai): move mock test data fetch to new ai package name
mikehardy Aug 12, 2025
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
440 changes: 440 additions & 0 deletions docs/ai/usage/index.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ export default [
'**/app.playground.js',
'**/type-test.ts',
'packages/**/modular/dist/**/*',
'packages/vertexai/__tests__/test-utils',
'packages/ai/__tests__/test-utils',
'packages/vertexai/dist',
'packages/ai/dist',
],
},
...compat
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint:spellcheck": "spellchecker --quiet --files=\"docs/**/*.md\" --dictionaries=\"./.spellcheck.dict.txt\" --reports=\"spelling.json\" --plugins spell indefinite-article repeated-words syntax-mentions syntax-urls frontmatter",
"tsc:compile": "tsc --project .",
"lint:all": "yarn lint && yarn lint:markdown && yarn lint:spellcheck && yarn tsc:compile",
"tests:vertex:mocks": "yarn ts-node ./scripts/fetch_ai_mock_responses.ts && yarn ts-node ./packages/vertexai/__tests__/test-utils/convert-mocks.ts",
"tests:ai:mocks": "yarn ts-node ./scripts/fetch_ai_mock_responses.ts && yarn ts-node ./packages/ai/__tests__/test-utils/convert-mocks.ts",
"tests:jest": "jest",
"tests:jest-watch": "jest --watch",
"tests:jest-coverage": "jest --coverage",
Expand Down
25 changes: 25 additions & 0 deletions packages/ai/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## Feature

Initial release of the Firebase AI Logic SDK (`FirebaseAI`). This SDK *replaces* the previous Vertex AI in Firebase SDK (`FirebaseVertexAI`) to accommodate the evolving set of supported features and services.
The new Firebase AI Logic SDK provides **preview** support for the Gemini Developer API, including its free tier offering.
Using the Firebase AI Logic SDK with the Vertex AI Gemini API is still generally available (GA).

To start using the new SDK, import the `@react-native-firebase/ai` package and use the modular method `getAI()` to initialize. See details in the [migration guide](https://firebase.google.com/docs/vertex-ai/migrate-to-latest-sdk).

Please update the following to move from VertexAI to FirebaseAI:

```js
// BEFORE - using firebase/vertexai
import { initializeApp } from "firebase/app";
~~import { getVertexAI, getGenerativeModel } from "firebase/vertexai";~~


// AFTER - using firebase/ai
import { initializeApp } from "firebase/app";
import { getAI, getGenerativeModel } from "firebase/ai";
```
32 changes: 32 additions & 0 deletions packages/ai/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Apache-2.0 License
------------------

Copyright (c) 2016-present Invertase Limited <oss@invertase.io> & Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this library except in compliance with the License.

You may obtain a copy of the Apache-2.0 License at

http://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.


Creative Commons Attribution 3.0 License
----------------------------------------

Copyright (c) 2016-present Invertase Limited <oss@invertase.io> & Contributors

Documentation and other instructional materials provided for this project
(including on a separate documentation repository or it's documentation website) are
licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks
contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above.

You may obtain a copy of the Creative Commons Attribution 3.0 License at

https://creativecommons.org/licenses/by/3.0/
55 changes: 55 additions & 0 deletions packages/ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<p align="center">
<a href="https://rnfirebase.io">
<img width="160px" src="https://i.imgur.com/JIyBtKW.png"><br/>
</a>
<h2 align="center">React Native Firebase - AI Logic</h2>
</p>

<p align="center">
<a href="https://api.rnfirebase.io/coverage/ai/detail"><img src="https://api.rnfirebase.io/coverage/ai/badge?style=flat-square" alt="Coverage"></a>
<a href="https://www.npmjs.com/package/@react-native-firebase/ai"><img src="https://img.shields.io/npm/dm/@react-native-firebase/ai.svg?style=flat-square" alt="NPM downloads"></a>
<a href="https://www.npmjs.com/package/@react-native-firebase/ai"><img src="https://img.shields.io/npm/v/@react-native-firebase/ai.svg?style=flat-square" alt="NPM version"></a>
<a href="/LICENSE"><img src="https://img.shields.io/npm/l/react-native-firebase.svg?style=flat-square" alt="License"></a>
<a href="https://lerna.js.org/"><img src="https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg?style=flat-square" alt="Maintained with Lerna"></a>
</p>

<p align="center">
<a href="https://invertase.link/discord"><img src="https://img.shields.io/discord/295953187817521152.svg?style=flat-square&colorA=7289da&label=Chat%20on%20Discord" alt="Chat on Discord"></a>
<a href="https://twitter.com/rnfirebase"><img src="https://img.shields.io/twitter/follow/rnfirebase.svg?style=flat-square&colorA=1da1f2&colorB=&label=Follow%20on%20Twitter" alt="Follow on Twitter"></a>
<a href="https://www.facebook.com/groups/rnfirebase"><img src="https://img.shields.io/badge/Follow%20on%20Facebook-4172B8?logo=facebook&style=flat-square&logoColor=fff" alt="Follow on Facebook"></a>
</p>

---

Firebase AI Logic gives you access to the latest generative AI models from Google.

If you need to call the Gemini API directly from your mobile or web app — rather than server-side — you can use the Firebase AI Logic client SDKs. These client SDKs are built specifically for use with mobile and web apps, offering security options against unauthorized clients as well as integrations with other Firebase services.

[> Learn More](https://firebase.google.com/docs/ai-logic/)

## Installation

Requires `@react-native-firebase/app` to be installed.

```bash
yarn add @react-native-firebase/ai
```

## Documentation

- [Quick Start](https://rnfirebase.io/ai/usage)

## License

- See [LICENSE](/LICENSE)

---

<p>
<img align="left" width="75px" src="https://static.invertase.io/assets/invertase-logo-small.png">
<p align="left">
Built and maintained with 💛 by <a href="https://invertase.io">Invertase</a>.
</p>
</p>

---
199 changes: 199 additions & 0 deletions packages/ai/__tests__/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* @license
* Copyright 2025 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
*
* http://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 { describe, expect, it } from '@jest/globals';
import { type ReactNativeFirebase } from '../../app/lib';

import { ModelParams, AIErrorCode } from '../lib/types';
import { AIError } from '../lib/errors';
import { getGenerativeModel } from '../lib/index';

import { AI } from '../lib/public-types';
import { GenerativeModel } from '../lib/models/generative-model';

import { AI_TYPE } from '../lib/constants';
import { GoogleAIBackend, VertexAIBackend } from '../lib/backend';

const fakeAI: AI = {
app: {
name: 'DEFAULT',
options: {
apiKey: 'key',
appId: 'appId',
projectId: 'my-project',
},
} as ReactNativeFirebase.FirebaseApp,
backend: new VertexAIBackend('us-central1'),
location: 'us-central1',
};

const fakeGoogleAI: AI = {
app: {
name: 'DEFAULT',
automaticDataCollectionEnabled: true,
options: {
apiKey: 'key',
projectId: 'my-project',
appId: 'my-appid',
},
} as ReactNativeFirebase.FirebaseApp,
backend: new GoogleAIBackend(),
location: 'us-central1',
};

describe('API tests', () => {
describe('getGenerativeModel() with VertexAIBackend', () => {
it('should throw an error if no model is provided', () => {
try {
getGenerativeModel(fakeAI, {} as ModelParams);
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_MODEL);
expect((e as AIError).message).toContain(
`AI: Must provide a model name. Example: ` +
`getGenerativeModel({ model: 'my-model-name' }) (${AI_TYPE}/${AIErrorCode.NO_MODEL})`,
);
}
});

it('getGenerativeModel throws if no apiKey is provided', () => {
const fakeVertexNoApiKey = {
...fakeAI,
app: { options: { projectId: 'my-project', appId: 'my-appid' } },
} as AI;
try {
getGenerativeModel(fakeVertexNoApiKey, { model: 'my-model' });
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_API_KEY);
expect((e as AIError).message).toBe(
`AI: The "apiKey" field is empty in the local ` +
`Firebase config. Firebase AI requires this field to` +
` contain a valid API key. (${AI_TYPE}/${AIErrorCode.NO_API_KEY})`,
);
}
});

it('should throw an error if no projectId is provided', () => {
const fakeVertexNoProject = {
...fakeAI,
app: { options: { apiKey: 'my-key' } },
} as AI;
try {
getGenerativeModel(fakeVertexNoProject, { model: 'my-model' });
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_PROJECT_ID);
expect((e as AIError).message).toBe(
`AI: The "projectId" field is empty in the local` +
` Firebase config. Firebase AI requires this field ` +
`to contain a valid project ID. (${AI_TYPE}/${AIErrorCode.NO_PROJECT_ID})`,
);
}
});

it('should throw an error if no appId is provided', () => {
const fakeVertexNoAppId = {
...fakeAI,
app: { options: { apiKey: 'my-key', projectId: 'my-projectid' } },
} as AI;
try {
getGenerativeModel(fakeVertexNoAppId, { model: 'my-model' });
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_APP_ID);
expect((e as AIError).message).toBe(
`AI: The "appId" field is empty in the local` +
` Firebase config. Firebase AI requires this field ` +
`to contain a valid app ID. (${AI_TYPE}/${AIErrorCode.NO_APP_ID})`,
);
}
});

it('should return an instance of GenerativeModel', () => {
const genModel = getGenerativeModel(fakeAI, { model: 'my-model' });
expect(genModel).toBeInstanceOf(GenerativeModel);
expect(genModel.model).toBe('publishers/google/models/my-model');
});
});

describe('getGenerativeModel() with GoogleAIBackend', () => {
it('should throw an error if no model is provided', () => {
try {
getGenerativeModel(fakeGoogleAI, {} as ModelParams);
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_MODEL);
expect((e as AIError).message).toContain(
`AI: Must provide a model name. Example: ` +
`getGenerativeModel({ model: 'my-model-name' }) (${AI_TYPE}/${AIErrorCode.NO_MODEL})`,
);
}
});

it('getGenerativeModel throws if no apiKey is provided', () => {
const fakeGoogleNoApiKey = {
...fakeGoogleAI,
app: { options: { projectId: 'my-project', appId: 'my-appid' } },
} as AI;
try {
getGenerativeModel(fakeGoogleNoApiKey, { model: 'my-model' });
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_API_KEY);
expect((e as AIError).message).toBe(
`AI: The "apiKey" field is empty in the local ` +
`Firebase config. Firebase AI requires this field to` +
` contain a valid API key. (${AI_TYPE}/${AIErrorCode.NO_API_KEY})`,
);
}
});

it('should throw an error if no projectId is provided', () => {
const fakeGoogleNoProject = {
...fakeGoogleAI,
app: { options: { apiKey: 'my-key' } },
} as AI;
try {
getGenerativeModel(fakeGoogleNoProject, { model: 'my-model' });
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_PROJECT_ID);
expect((e as AIError).message).toBe(
`AI: The "projectId" field is empty in the local` +
` Firebase config. Firebase AI requires this field ` +
`to contain a valid project ID. (${AI_TYPE}/${AIErrorCode.NO_PROJECT_ID})`,
);
}
});

it('should throw an error if no appId is provided', () => {
const fakeGoogleNoAppId = {
...fakeGoogleAI,
app: { options: { apiKey: 'my-key', projectId: 'my-projectid' } },
} as AI;
try {
getGenerativeModel(fakeGoogleNoAppId, { model: 'my-model' });
} catch (e) {
expect((e as AIError).code).toContain(AIErrorCode.NO_APP_ID);
expect((e as AIError).message).toBe(
`AI: The "appId" field is empty in the local` +
` Firebase config. Firebase AI requires this field ` +
`to contain a valid app ID. (${AI_TYPE}/${AIErrorCode.NO_APP_ID})`,
);
}
});

it('should return an instance of GenerativeModel', () => {
const genModel = getGenerativeModel(fakeGoogleAI, { model: 'my-model' });
expect(genModel).toBeInstanceOf(GenerativeModel);
expect(genModel.model).toBe('models/my-model');
});
});
});
55 changes: 55 additions & 0 deletions packages/ai/__tests__/backend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license
* Copyright 2025 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
*
* http://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 { describe, it, expect } from '@jest/globals';
import { GoogleAIBackend, VertexAIBackend } from '../lib/backend';
import { BackendType } from '../lib/public-types';
import { DEFAULT_LOCATION } from '../lib/constants';

describe('Backend', () => {
describe('GoogleAIBackend', () => {
it('should set backendType to GOOGLE_AI', () => {
const backend = new GoogleAIBackend();
expect(backend.backendType).toBe(BackendType.GOOGLE_AI);
});
});

describe('VertexAIBackend', () => {
it('should set backendType to VERTEX_AI', () => {
const backend = new VertexAIBackend();
expect(backend.backendType).toBe(BackendType.VERTEX_AI);
expect(backend.location).toBe(DEFAULT_LOCATION);
});

it('should set a custom location', () => {
const backend = new VertexAIBackend('test-location');
expect(backend.backendType).toBe(BackendType.VERTEX_AI);
expect(backend.location).toBe('test-location');
});

it('should use a default location if location is empty string', () => {
const backend = new VertexAIBackend('');
expect(backend.backendType).toBe(BackendType.VERTEX_AI);
expect(backend.location).toBe(DEFAULT_LOCATION);
});

it('uses default location if location is null', () => {
const backend = new VertexAIBackend(null as any);
expect(backend.backendType).toBe(BackendType.VERTEX_AI);
expect(backend.location).toBe(DEFAULT_LOCATION);
});
});
});
Loading
Loading