@firebase/ai 2.0.0 → 2.1.0-canary.02280d747
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-public.d.ts +217 -9
- package/dist/ai.d.ts +220 -10
- package/dist/esm/index.esm.js +394 -34
- package/dist/esm/index.esm.js.map +1 -1
- package/dist/esm/src/api.d.ts +2 -2
- package/dist/esm/src/constants.d.ts +4 -0
- package/dist/esm/src/index.d.ts +3 -0
- package/dist/esm/src/methods/chat-session.d.ts +3 -1
- package/dist/esm/src/methods/chrome-adapter.d.ts +120 -0
- package/dist/esm/src/methods/count-tokens.d.ts +3 -1
- package/dist/esm/src/methods/generate-content.d.ts +3 -2
- package/dist/esm/src/models/ai-model.d.ts +1 -1
- package/dist/esm/src/models/generative-model.d.ts +3 -1
- package/dist/esm/src/public-types.d.ts +10 -1
- package/dist/esm/src/service.d.ts +4 -1
- package/dist/esm/src/types/chrome-adapter.d.ts +56 -0
- package/dist/esm/src/types/enums.d.ts +20 -1
- package/dist/esm/src/types/imagen/requests.d.ts +2 -2
- package/dist/esm/src/types/imagen/responses.d.ts +1 -0
- package/dist/esm/src/types/index.d.ts +2 -0
- package/dist/esm/src/types/language-model.d.ts +117 -0
- package/dist/esm/src/types/requests.d.ts +35 -2
- package/dist/esm/src/types/responses.d.ts +1 -1
- package/dist/esm/src/types/schema.d.ts +1 -1
- package/dist/index.cjs.js +395 -33
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.node.cjs.js +382 -22
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +382 -23
- package/dist/index.node.mjs.map +1 -1
- package/dist/src/api.d.ts +2 -2
- package/dist/src/constants.d.ts +4 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/methods/chat-session.d.ts +3 -1
- package/dist/src/methods/chrome-adapter.d.ts +120 -0
- package/dist/src/methods/count-tokens.d.ts +3 -1
- package/dist/src/methods/generate-content.d.ts +3 -2
- package/dist/src/models/ai-model.d.ts +1 -1
- package/dist/src/models/generative-model.d.ts +3 -1
- package/dist/src/public-types.d.ts +10 -1
- package/dist/src/service.d.ts +4 -1
- package/dist/src/types/chrome-adapter.d.ts +56 -0
- package/dist/src/types/enums.d.ts +20 -1
- package/dist/src/types/imagen/requests.d.ts +2 -2
- package/dist/src/types/imagen/responses.d.ts +1 -0
- package/dist/src/types/index.d.ts +2 -0
- package/dist/src/types/language-model.d.ts +117 -0
- package/dist/src/types/requests.d.ts +35 -2
- package/dist/src/types/responses.d.ts +1 -1
- package/dist/src/types/schema.d.ts +1 -1
- package/package.json +8 -8
package/dist/esm/index.esm.js
CHANGED
|
@@ -4,7 +4,7 @@ import { FirebaseError, getModularInstance } from '@firebase/util';
|
|
|
4
4
|
import { Logger } from '@firebase/logger';
|
|
5
5
|
|
|
6
6
|
var name = "@firebase/ai";
|
|
7
|
-
var version = "2.0.
|
|
7
|
+
var version = "2.1.0-canary.02280d747";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @license
|
|
@@ -29,6 +29,10 @@ const DEFAULT_API_VERSION = 'v1beta';
|
|
|
29
29
|
const PACKAGE_VERSION = version;
|
|
30
30
|
const LANGUAGE_TAG = 'gl-js';
|
|
31
31
|
const DEFAULT_FETCH_TIMEOUT_MS = 180 * 1000;
|
|
32
|
+
/**
|
|
33
|
+
* Defines the name of the default in-cloud model to use for hybrid inference.
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.0-flash-lite';
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
38
|
* @license
|
|
@@ -84,7 +88,7 @@ const HarmBlockThreshold = {
|
|
|
84
88
|
BLOCK_NONE: 'BLOCK_NONE',
|
|
85
89
|
/**
|
|
86
90
|
* All content will be allowed. This is the same as `BLOCK_NONE`, but the metadata corresponding
|
|
87
|
-
* to the {@link HarmCategory} will not be present in the response.
|
|
91
|
+
* to the {@link (HarmCategory:type)} will not be present in the response.
|
|
88
92
|
*/
|
|
89
93
|
OFF: 'OFF'
|
|
90
94
|
};
|
|
@@ -287,6 +291,16 @@ const ResponseModality = {
|
|
|
287
291
|
*/
|
|
288
292
|
IMAGE: 'IMAGE'
|
|
289
293
|
};
|
|
294
|
+
/**
|
|
295
|
+
* <b>(EXPERIMENTAL)</b>
|
|
296
|
+
* Determines whether inference happens on-device or in-cloud.
|
|
297
|
+
* @public
|
|
298
|
+
*/
|
|
299
|
+
const InferenceMode = {
|
|
300
|
+
'PREFER_ON_DEVICE': 'prefer_on_device',
|
|
301
|
+
'ONLY_ON_DEVICE': 'only_on_device',
|
|
302
|
+
'ONLY_IN_CLOUD': 'only_in_cloud'
|
|
303
|
+
};
|
|
290
304
|
|
|
291
305
|
/**
|
|
292
306
|
* @license
|
|
@@ -460,7 +474,7 @@ const ImagenPersonFilterLevel = {
|
|
|
460
474
|
* To specify an aspect ratio for generated images, set the `aspectRatio` property in your
|
|
461
475
|
* {@link ImagenGenerationConfig}.
|
|
462
476
|
*
|
|
463
|
-
* See the
|
|
477
|
+
* See the {@link http://firebase.google.com/docs/vertex-ai/generate-images | documentation }
|
|
464
478
|
* for more details and examples of the supported aspect ratios.
|
|
465
479
|
*
|
|
466
480
|
* @beta
|
|
@@ -639,6 +653,12 @@ class AIService {
|
|
|
639
653
|
_delete() {
|
|
640
654
|
return Promise.resolve();
|
|
641
655
|
}
|
|
656
|
+
set options(optionsToSet) {
|
|
657
|
+
this._options = optionsToSet;
|
|
658
|
+
}
|
|
659
|
+
get options() {
|
|
660
|
+
return this._options;
|
|
661
|
+
}
|
|
642
662
|
}
|
|
643
663
|
|
|
644
664
|
/**
|
|
@@ -823,7 +843,12 @@ class AIModel {
|
|
|
823
843
|
};
|
|
824
844
|
}
|
|
825
845
|
else if (ai.appCheck) {
|
|
826
|
-
|
|
846
|
+
if (ai.options?.useLimitedUseAppCheckTokens) {
|
|
847
|
+
this._apiSettings.getAppCheckToken = () => ai.appCheck.getLimitedUseToken();
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
this._apiSettings.getAppCheckToken = () => ai.appCheck.getToken();
|
|
851
|
+
}
|
|
827
852
|
}
|
|
828
853
|
if (ai.auth) {
|
|
829
854
|
this._apiSettings.getAuthToken = () => ai.auth.getToken();
|
|
@@ -1660,20 +1685,38 @@ function aggregateResponses(responses) {
|
|
|
1660
1685
|
* See the License for the specific language governing permissions and
|
|
1661
1686
|
* limitations under the License.
|
|
1662
1687
|
*/
|
|
1663
|
-
async function
|
|
1688
|
+
async function generateContentStreamOnCloud(apiSettings, model, params, requestOptions) {
|
|
1664
1689
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
1665
1690
|
params = mapGenerateContentRequest(params);
|
|
1666
1691
|
}
|
|
1667
|
-
|
|
1692
|
+
return makeRequest(model, Task.STREAM_GENERATE_CONTENT, apiSettings,
|
|
1668
1693
|
/* stream */ true, JSON.stringify(params), requestOptions);
|
|
1694
|
+
}
|
|
1695
|
+
async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
1696
|
+
let response;
|
|
1697
|
+
if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
|
|
1698
|
+
response = await chromeAdapter.generateContentStream(params);
|
|
1699
|
+
}
|
|
1700
|
+
else {
|
|
1701
|
+
response = await generateContentStreamOnCloud(apiSettings, model, params, requestOptions);
|
|
1702
|
+
}
|
|
1669
1703
|
return processStream(response, apiSettings); // TODO: Map streaming responses
|
|
1670
1704
|
}
|
|
1671
|
-
async function
|
|
1705
|
+
async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
|
|
1672
1706
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
1673
1707
|
params = mapGenerateContentRequest(params);
|
|
1674
1708
|
}
|
|
1675
|
-
|
|
1709
|
+
return makeRequest(model, Task.GENERATE_CONTENT, apiSettings,
|
|
1676
1710
|
/* stream */ false, JSON.stringify(params), requestOptions);
|
|
1711
|
+
}
|
|
1712
|
+
async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
1713
|
+
let response;
|
|
1714
|
+
if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
|
|
1715
|
+
response = await chromeAdapter.generateContent(params);
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
response = await generateContentOnCloud(apiSettings, model, params, requestOptions);
|
|
1719
|
+
}
|
|
1677
1720
|
const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
|
|
1678
1721
|
const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
|
|
1679
1722
|
return {
|
|
@@ -1930,8 +1973,9 @@ const SILENT_ERROR = 'SILENT_ERROR';
|
|
|
1930
1973
|
* @public
|
|
1931
1974
|
*/
|
|
1932
1975
|
class ChatSession {
|
|
1933
|
-
constructor(apiSettings, model, params, requestOptions) {
|
|
1976
|
+
constructor(apiSettings, model, chromeAdapter, params, requestOptions) {
|
|
1934
1977
|
this.model = model;
|
|
1978
|
+
this.chromeAdapter = chromeAdapter;
|
|
1935
1979
|
this.params = params;
|
|
1936
1980
|
this.requestOptions = requestOptions;
|
|
1937
1981
|
this._history = [];
|
|
@@ -1969,7 +2013,7 @@ class ChatSession {
|
|
|
1969
2013
|
let finalResult = {};
|
|
1970
2014
|
// Add onto the chain.
|
|
1971
2015
|
this._sendPromise = this._sendPromise
|
|
1972
|
-
.then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.requestOptions))
|
|
2016
|
+
.then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions))
|
|
1973
2017
|
.then(result => {
|
|
1974
2018
|
if (result.response.candidates &&
|
|
1975
2019
|
result.response.candidates.length > 0) {
|
|
@@ -2008,7 +2052,7 @@ class ChatSession {
|
|
|
2008
2052
|
systemInstruction: this.params?.systemInstruction,
|
|
2009
2053
|
contents: [...this._history, newContent]
|
|
2010
2054
|
};
|
|
2011
|
-
const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.requestOptions);
|
|
2055
|
+
const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions);
|
|
2012
2056
|
// Add onto the chain.
|
|
2013
2057
|
this._sendPromise = this._sendPromise
|
|
2014
2058
|
.then(() => streamPromise)
|
|
@@ -2065,7 +2109,7 @@ class ChatSession {
|
|
|
2065
2109
|
* See the License for the specific language governing permissions and
|
|
2066
2110
|
* limitations under the License.
|
|
2067
2111
|
*/
|
|
2068
|
-
async function
|
|
2112
|
+
async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
|
|
2069
2113
|
let body = '';
|
|
2070
2114
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
2071
2115
|
const mappedParams = mapCountTokensRequest(params, model);
|
|
@@ -2077,6 +2121,12 @@ async function countTokens(apiSettings, model, params, requestOptions) {
|
|
|
2077
2121
|
const response = await makeRequest(model, Task.COUNT_TOKENS, apiSettings, false, body, requestOptions);
|
|
2078
2122
|
return response.json();
|
|
2079
2123
|
}
|
|
2124
|
+
async function countTokens(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
2125
|
+
if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
|
|
2126
|
+
return (await chromeAdapter.countTokens(params)).json();
|
|
2127
|
+
}
|
|
2128
|
+
return countTokensOnCloud(apiSettings, model, params, requestOptions);
|
|
2129
|
+
}
|
|
2080
2130
|
|
|
2081
2131
|
/**
|
|
2082
2132
|
* @license
|
|
@@ -2099,8 +2149,9 @@ async function countTokens(apiSettings, model, params, requestOptions) {
|
|
|
2099
2149
|
* @public
|
|
2100
2150
|
*/
|
|
2101
2151
|
class GenerativeModel extends AIModel {
|
|
2102
|
-
constructor(ai, modelParams, requestOptions) {
|
|
2152
|
+
constructor(ai, modelParams, requestOptions, chromeAdapter) {
|
|
2103
2153
|
super(ai, modelParams.model);
|
|
2154
|
+
this.chromeAdapter = chromeAdapter;
|
|
2104
2155
|
this.generationConfig = modelParams.generationConfig || {};
|
|
2105
2156
|
this.safetySettings = modelParams.safetySettings || [];
|
|
2106
2157
|
this.tools = modelParams.tools;
|
|
@@ -2121,7 +2172,7 @@ class GenerativeModel extends AIModel {
|
|
|
2121
2172
|
toolConfig: this.toolConfig,
|
|
2122
2173
|
systemInstruction: this.systemInstruction,
|
|
2123
2174
|
...formattedParams
|
|
2124
|
-
}, this.requestOptions);
|
|
2175
|
+
}, this.chromeAdapter, this.requestOptions);
|
|
2125
2176
|
}
|
|
2126
2177
|
/**
|
|
2127
2178
|
* Makes a single streaming call to the model
|
|
@@ -2138,14 +2189,14 @@ class GenerativeModel extends AIModel {
|
|
|
2138
2189
|
toolConfig: this.toolConfig,
|
|
2139
2190
|
systemInstruction: this.systemInstruction,
|
|
2140
2191
|
...formattedParams
|
|
2141
|
-
}, this.requestOptions);
|
|
2192
|
+
}, this.chromeAdapter, this.requestOptions);
|
|
2142
2193
|
}
|
|
2143
2194
|
/**
|
|
2144
2195
|
* Gets a new {@link ChatSession} instance which can be used for
|
|
2145
2196
|
* multi-turn chats.
|
|
2146
2197
|
*/
|
|
2147
2198
|
startChat(startChatParams) {
|
|
2148
|
-
return new ChatSession(this._apiSettings, this.model, {
|
|
2199
|
+
return new ChatSession(this._apiSettings, this.model, this.chromeAdapter, {
|
|
2149
2200
|
tools: this.tools,
|
|
2150
2201
|
toolConfig: this.toolConfig,
|
|
2151
2202
|
systemInstruction: this.systemInstruction,
|
|
@@ -2164,7 +2215,7 @@ class GenerativeModel extends AIModel {
|
|
|
2164
2215
|
*/
|
|
2165
2216
|
async countTokens(request) {
|
|
2166
2217
|
const formattedParams = formatGenerateContentInput(request);
|
|
2167
|
-
return countTokens(this._apiSettings, this.model, formattedParams);
|
|
2218
|
+
return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter);
|
|
2168
2219
|
}
|
|
2169
2220
|
}
|
|
2170
2221
|
|
|
@@ -2282,6 +2333,292 @@ class ImagenModel extends AIModel {
|
|
|
2282
2333
|
}
|
|
2283
2334
|
}
|
|
2284
2335
|
|
|
2336
|
+
/**
|
|
2337
|
+
* @internal
|
|
2338
|
+
*/
|
|
2339
|
+
var Availability;
|
|
2340
|
+
(function (Availability) {
|
|
2341
|
+
Availability["UNAVAILABLE"] = "unavailable";
|
|
2342
|
+
Availability["DOWNLOADABLE"] = "downloadable";
|
|
2343
|
+
Availability["DOWNLOADING"] = "downloading";
|
|
2344
|
+
Availability["AVAILABLE"] = "available";
|
|
2345
|
+
})(Availability || (Availability = {}));
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* @license
|
|
2349
|
+
* Copyright 2025 Google LLC
|
|
2350
|
+
*
|
|
2351
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2352
|
+
* you may not use this file except in compliance with the License.
|
|
2353
|
+
* You may obtain a copy of the License at
|
|
2354
|
+
*
|
|
2355
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
2356
|
+
*
|
|
2357
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2358
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
2359
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2360
|
+
* See the License for the specific language governing permissions and
|
|
2361
|
+
* limitations under the License.
|
|
2362
|
+
*/
|
|
2363
|
+
/**
|
|
2364
|
+
* Defines an inference "backend" that uses Chrome's on-device model,
|
|
2365
|
+
* and encapsulates logic for detecting when on-device inference is
|
|
2366
|
+
* possible.
|
|
2367
|
+
*/
|
|
2368
|
+
class ChromeAdapterImpl {
|
|
2369
|
+
constructor(languageModelProvider, mode, onDeviceParams = {
|
|
2370
|
+
createOptions: {
|
|
2371
|
+
// Defaults to support image inputs for convenience.
|
|
2372
|
+
expectedInputs: [{ type: 'image' }]
|
|
2373
|
+
}
|
|
2374
|
+
}) {
|
|
2375
|
+
this.languageModelProvider = languageModelProvider;
|
|
2376
|
+
this.mode = mode;
|
|
2377
|
+
this.onDeviceParams = onDeviceParams;
|
|
2378
|
+
this.isDownloading = false;
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Checks if a given request can be made on-device.
|
|
2382
|
+
*
|
|
2383
|
+
* Encapsulates a few concerns:
|
|
2384
|
+
* the mode
|
|
2385
|
+
* API existence
|
|
2386
|
+
* prompt formatting
|
|
2387
|
+
* model availability, including triggering download if necessary
|
|
2388
|
+
*
|
|
2389
|
+
*
|
|
2390
|
+
* Pros: callers needn't be concerned with details of on-device availability.</p>
|
|
2391
|
+
* Cons: this method spans a few concerns and splits request validation from usage.
|
|
2392
|
+
* If instance variables weren't already part of the API, we could consider a better
|
|
2393
|
+
* separation of concerns.
|
|
2394
|
+
*/
|
|
2395
|
+
async isAvailable(request) {
|
|
2396
|
+
if (!this.mode) {
|
|
2397
|
+
logger.debug(`On-device inference unavailable because mode is undefined.`);
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
if (this.mode === InferenceMode.ONLY_IN_CLOUD) {
|
|
2401
|
+
logger.debug(`On-device inference unavailable because mode is "only_in_cloud".`);
|
|
2402
|
+
return false;
|
|
2403
|
+
}
|
|
2404
|
+
// Triggers out-of-band download so model will eventually become available.
|
|
2405
|
+
const availability = await this.downloadIfAvailable();
|
|
2406
|
+
if (this.mode === InferenceMode.ONLY_ON_DEVICE) {
|
|
2407
|
+
// If it will never be available due to API inavailability, throw.
|
|
2408
|
+
if (availability === Availability.UNAVAILABLE) {
|
|
2409
|
+
throw new AIError(AIErrorCode.API_NOT_ENABLED, 'Local LanguageModel API not available in this environment.');
|
|
2410
|
+
}
|
|
2411
|
+
else if (availability === Availability.DOWNLOADABLE ||
|
|
2412
|
+
availability === Availability.DOWNLOADING) {
|
|
2413
|
+
// TODO(chholland): Better user experience during download - progress?
|
|
2414
|
+
logger.debug(`Waiting for download of LanguageModel to complete.`);
|
|
2415
|
+
await this.downloadPromise;
|
|
2416
|
+
return true;
|
|
2417
|
+
}
|
|
2418
|
+
return true;
|
|
2419
|
+
}
|
|
2420
|
+
// Applies prefer_on_device logic.
|
|
2421
|
+
if (availability !== Availability.AVAILABLE) {
|
|
2422
|
+
logger.debug(`On-device inference unavailable because availability is "${availability}".`);
|
|
2423
|
+
return false;
|
|
2424
|
+
}
|
|
2425
|
+
if (!ChromeAdapterImpl.isOnDeviceRequest(request)) {
|
|
2426
|
+
logger.debug(`On-device inference unavailable because request is incompatible.`);
|
|
2427
|
+
return false;
|
|
2428
|
+
}
|
|
2429
|
+
return true;
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Generates content on device.
|
|
2433
|
+
*
|
|
2434
|
+
* @remarks
|
|
2435
|
+
* This is comparable to {@link GenerativeModel.generateContent} for generating content in
|
|
2436
|
+
* Cloud.
|
|
2437
|
+
* @param request - a standard Firebase AI {@link GenerateContentRequest}
|
|
2438
|
+
* @returns {@link Response}, so we can reuse common response formatting.
|
|
2439
|
+
*/
|
|
2440
|
+
async generateContent(request) {
|
|
2441
|
+
const session = await this.createSession();
|
|
2442
|
+
const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
|
|
2443
|
+
const text = await session.prompt(contents, this.onDeviceParams.promptOptions);
|
|
2444
|
+
return ChromeAdapterImpl.toResponse(text);
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Generates content stream on device.
|
|
2448
|
+
*
|
|
2449
|
+
* @remarks
|
|
2450
|
+
* This is comparable to {@link GenerativeModel.generateContentStream} for generating content in
|
|
2451
|
+
* Cloud.
|
|
2452
|
+
* @param request - a standard Firebase AI {@link GenerateContentRequest}
|
|
2453
|
+
* @returns {@link Response}, so we can reuse common response formatting.
|
|
2454
|
+
*/
|
|
2455
|
+
async generateContentStream(request) {
|
|
2456
|
+
const session = await this.createSession();
|
|
2457
|
+
const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
|
|
2458
|
+
const stream = session.promptStreaming(contents, this.onDeviceParams.promptOptions);
|
|
2459
|
+
return ChromeAdapterImpl.toStreamResponse(stream);
|
|
2460
|
+
}
|
|
2461
|
+
async countTokens(_request) {
|
|
2462
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, 'Count Tokens is not yet available for on-device model.');
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Asserts inference for the given request can be performed by an on-device model.
|
|
2466
|
+
*/
|
|
2467
|
+
static isOnDeviceRequest(request) {
|
|
2468
|
+
// Returns false if the prompt is empty.
|
|
2469
|
+
if (request.contents.length === 0) {
|
|
2470
|
+
logger.debug('Empty prompt rejected for on-device inference.');
|
|
2471
|
+
return false;
|
|
2472
|
+
}
|
|
2473
|
+
for (const content of request.contents) {
|
|
2474
|
+
if (content.role === 'function') {
|
|
2475
|
+
logger.debug(`"Function" role rejected for on-device inference.`);
|
|
2476
|
+
return false;
|
|
2477
|
+
}
|
|
2478
|
+
// Returns false if request contains an image with an unsupported mime type.
|
|
2479
|
+
for (const part of content.parts) {
|
|
2480
|
+
if (part.inlineData &&
|
|
2481
|
+
ChromeAdapterImpl.SUPPORTED_MIME_TYPES.indexOf(part.inlineData.mimeType) === -1) {
|
|
2482
|
+
logger.debug(`Unsupported mime type "${part.inlineData.mimeType}" rejected for on-device inference.`);
|
|
2483
|
+
return false;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return true;
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* Encapsulates logic to get availability and download a model if one is downloadable.
|
|
2491
|
+
*/
|
|
2492
|
+
async downloadIfAvailable() {
|
|
2493
|
+
const availability = await this.languageModelProvider?.availability(this.onDeviceParams.createOptions);
|
|
2494
|
+
if (availability === Availability.DOWNLOADABLE) {
|
|
2495
|
+
this.download();
|
|
2496
|
+
}
|
|
2497
|
+
return availability;
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Triggers out-of-band download of an on-device model.
|
|
2501
|
+
*
|
|
2502
|
+
* Chrome only downloads models as needed. Chrome knows a model is needed when code calls
|
|
2503
|
+
* LanguageModel.create.
|
|
2504
|
+
*
|
|
2505
|
+
* Since Chrome manages the download, the SDK can only avoid redundant download requests by
|
|
2506
|
+
* tracking if a download has previously been requested.
|
|
2507
|
+
*/
|
|
2508
|
+
download() {
|
|
2509
|
+
if (this.isDownloading) {
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
this.isDownloading = true;
|
|
2513
|
+
this.downloadPromise = this.languageModelProvider
|
|
2514
|
+
?.create(this.onDeviceParams.createOptions)
|
|
2515
|
+
.finally(() => {
|
|
2516
|
+
this.isDownloading = false;
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Converts Firebase AI {@link Content} object to a Chrome {@link LanguageModelMessage} object.
|
|
2521
|
+
*/
|
|
2522
|
+
static async toLanguageModelMessage(content) {
|
|
2523
|
+
const languageModelMessageContents = await Promise.all(content.parts.map(ChromeAdapterImpl.toLanguageModelMessageContent));
|
|
2524
|
+
return {
|
|
2525
|
+
role: ChromeAdapterImpl.toLanguageModelMessageRole(content.role),
|
|
2526
|
+
content: languageModelMessageContents
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Converts a Firebase AI Part object to a Chrome LanguageModelMessageContent object.
|
|
2531
|
+
*/
|
|
2532
|
+
static async toLanguageModelMessageContent(part) {
|
|
2533
|
+
if (part.text) {
|
|
2534
|
+
return {
|
|
2535
|
+
type: 'text',
|
|
2536
|
+
value: part.text
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
else if (part.inlineData) {
|
|
2540
|
+
const formattedImageContent = await fetch(`data:${part.inlineData.mimeType};base64,${part.inlineData.data}`);
|
|
2541
|
+
const imageBlob = await formattedImageContent.blob();
|
|
2542
|
+
const imageBitmap = await createImageBitmap(imageBlob);
|
|
2543
|
+
return {
|
|
2544
|
+
type: 'image',
|
|
2545
|
+
value: imageBitmap
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, `Processing of this Part type is not currently supported.`);
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Converts a Firebase AI {@link Role} string to a {@link LanguageModelMessageRole} string.
|
|
2552
|
+
*/
|
|
2553
|
+
static toLanguageModelMessageRole(role) {
|
|
2554
|
+
// Assumes 'function' rule has been filtered by isOnDeviceRequest
|
|
2555
|
+
return role === 'model' ? 'assistant' : 'user';
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* Abstracts Chrome session creation.
|
|
2559
|
+
*
|
|
2560
|
+
* Chrome uses a multi-turn session for all inference. Firebase AI uses single-turn for all
|
|
2561
|
+
* inference. To map the Firebase AI API to Chrome's API, the SDK creates a new session for all
|
|
2562
|
+
* inference.
|
|
2563
|
+
*
|
|
2564
|
+
* Chrome will remove a model from memory if it's no longer in use, so this method ensures a
|
|
2565
|
+
* new session is created before an old session is destroyed.
|
|
2566
|
+
*/
|
|
2567
|
+
async createSession() {
|
|
2568
|
+
if (!this.languageModelProvider) {
|
|
2569
|
+
throw new AIError(AIErrorCode.UNSUPPORTED, 'Chrome AI requested for unsupported browser version.');
|
|
2570
|
+
}
|
|
2571
|
+
const newSession = await this.languageModelProvider.create(this.onDeviceParams.createOptions);
|
|
2572
|
+
if (this.oldSession) {
|
|
2573
|
+
this.oldSession.destroy();
|
|
2574
|
+
}
|
|
2575
|
+
// Holds session reference, so model isn't unloaded from memory.
|
|
2576
|
+
this.oldSession = newSession;
|
|
2577
|
+
return newSession;
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Formats string returned by Chrome as a {@link Response} returned by Firebase AI.
|
|
2581
|
+
*/
|
|
2582
|
+
static toResponse(text) {
|
|
2583
|
+
return {
|
|
2584
|
+
json: async () => ({
|
|
2585
|
+
candidates: [
|
|
2586
|
+
{
|
|
2587
|
+
content: {
|
|
2588
|
+
parts: [{ text }]
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
]
|
|
2592
|
+
})
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Formats string stream returned by Chrome as SSE returned by Firebase AI.
|
|
2597
|
+
*/
|
|
2598
|
+
static toStreamResponse(stream) {
|
|
2599
|
+
const encoder = new TextEncoder();
|
|
2600
|
+
return {
|
|
2601
|
+
body: stream.pipeThrough(new TransformStream({
|
|
2602
|
+
transform(chunk, controller) {
|
|
2603
|
+
const json = JSON.stringify({
|
|
2604
|
+
candidates: [
|
|
2605
|
+
{
|
|
2606
|
+
content: {
|
|
2607
|
+
role: 'model',
|
|
2608
|
+
parts: [{ text: chunk }]
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
]
|
|
2612
|
+
});
|
|
2613
|
+
controller.enqueue(encoder.encode(`data: ${json}\n\n`));
|
|
2614
|
+
}
|
|
2615
|
+
}))
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
// Visible for testing
|
|
2620
|
+
ChromeAdapterImpl.SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/png'];
|
|
2621
|
+
|
|
2285
2622
|
/**
|
|
2286
2623
|
* @license
|
|
2287
2624
|
* Copyright 2024 Google LLC
|
|
@@ -2633,14 +2970,20 @@ class ImagenImageFormat {
|
|
|
2633
2970
|
*
|
|
2634
2971
|
* @public
|
|
2635
2972
|
*/
|
|
2636
|
-
function getAI(app = getApp(), options
|
|
2973
|
+
function getAI(app = getApp(), options) {
|
|
2637
2974
|
app = getModularInstance(app);
|
|
2638
2975
|
// Dependencies
|
|
2639
2976
|
const AIProvider = _getProvider(app, AI_TYPE);
|
|
2640
|
-
const
|
|
2641
|
-
|
|
2977
|
+
const backend = options?.backend ?? new GoogleAIBackend();
|
|
2978
|
+
const finalOptions = {
|
|
2979
|
+
useLimitedUseAppCheckTokens: options?.useLimitedUseAppCheckTokens ?? false
|
|
2980
|
+
};
|
|
2981
|
+
const identifier = encodeInstanceIdentifier(backend);
|
|
2982
|
+
const aiInstance = AIProvider.getImmediate({
|
|
2642
2983
|
identifier
|
|
2643
2984
|
});
|
|
2985
|
+
aiInstance.options = finalOptions;
|
|
2986
|
+
return aiInstance;
|
|
2644
2987
|
}
|
|
2645
2988
|
/**
|
|
2646
2989
|
* Returns a {@link GenerativeModel} class with methods for inference
|
|
@@ -2649,10 +2992,26 @@ function getAI(app = getApp(), options = { backend: new GoogleAIBackend() }) {
|
|
|
2649
2992
|
* @public
|
|
2650
2993
|
*/
|
|
2651
2994
|
function getGenerativeModel(ai, modelParams, requestOptions) {
|
|
2652
|
-
|
|
2995
|
+
// Uses the existence of HybridParams.mode to clarify the type of the modelParams input.
|
|
2996
|
+
const hybridParams = modelParams;
|
|
2997
|
+
let inCloudParams;
|
|
2998
|
+
if (hybridParams.mode) {
|
|
2999
|
+
inCloudParams = hybridParams.inCloudParams || {
|
|
3000
|
+
model: DEFAULT_HYBRID_IN_CLOUD_MODEL
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
else {
|
|
3004
|
+
inCloudParams = modelParams;
|
|
3005
|
+
}
|
|
3006
|
+
if (!inCloudParams.model) {
|
|
2653
3007
|
throw new AIError(AIErrorCode.NO_MODEL, `Must provide a model name. Example: getGenerativeModel({ model: 'my-model-name' })`);
|
|
2654
3008
|
}
|
|
2655
|
-
|
|
3009
|
+
let chromeAdapter;
|
|
3010
|
+
// Do not initialize a ChromeAdapter if we are not in hybrid mode.
|
|
3011
|
+
if (typeof window !== 'undefined' && hybridParams.mode) {
|
|
3012
|
+
chromeAdapter = new ChromeAdapterImpl(window.LanguageModel, hybridParams.mode, hybridParams.onDeviceParams);
|
|
3013
|
+
}
|
|
3014
|
+
return new GenerativeModel(ai, inCloudParams, requestOptions, chromeAdapter);
|
|
2656
3015
|
}
|
|
2657
3016
|
/**
|
|
2658
3017
|
* Returns an {@link ImagenModel} class with methods for using Imagen.
|
|
@@ -2680,23 +3039,24 @@ function getImagenModel(ai, modelParams, requestOptions) {
|
|
|
2680
3039
|
*
|
|
2681
3040
|
* @packageDocumentation
|
|
2682
3041
|
*/
|
|
3042
|
+
function factory(container, { instanceIdentifier }) {
|
|
3043
|
+
if (!instanceIdentifier) {
|
|
3044
|
+
throw new AIError(AIErrorCode.ERROR, 'AIService instance identifier is undefined.');
|
|
3045
|
+
}
|
|
3046
|
+
const backend = decodeInstanceIdentifier(instanceIdentifier);
|
|
3047
|
+
// getImmediate for FirebaseApp will always succeed
|
|
3048
|
+
const app = container.getProvider('app').getImmediate();
|
|
3049
|
+
const auth = container.getProvider('auth-internal');
|
|
3050
|
+
const appCheckProvider = container.getProvider('app-check-internal');
|
|
3051
|
+
return new AIService(app, backend, auth, appCheckProvider);
|
|
3052
|
+
}
|
|
2683
3053
|
function registerAI() {
|
|
2684
|
-
_registerComponent(new Component(AI_TYPE,
|
|
2685
|
-
if (!instanceIdentifier) {
|
|
2686
|
-
throw new AIError(AIErrorCode.ERROR, 'AIService instance identifier is undefined.');
|
|
2687
|
-
}
|
|
2688
|
-
const backend = decodeInstanceIdentifier(instanceIdentifier);
|
|
2689
|
-
// getImmediate for FirebaseApp will always succeed
|
|
2690
|
-
const app = container.getProvider('app').getImmediate();
|
|
2691
|
-
const auth = container.getProvider('auth-internal');
|
|
2692
|
-
const appCheckProvider = container.getProvider('app-check-internal');
|
|
2693
|
-
return new AIService(app, backend, auth, appCheckProvider);
|
|
2694
|
-
}, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
3054
|
+
_registerComponent(new Component(AI_TYPE, factory, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
2695
3055
|
registerVersion(name, version);
|
|
2696
3056
|
// BUILD_TARGET will be replaced by values like esm, cjs, etc during the compilation
|
|
2697
3057
|
registerVersion(name, version, 'esm2020');
|
|
2698
3058
|
}
|
|
2699
3059
|
registerAI();
|
|
2700
3060
|
|
|
2701
|
-
export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, FinishReason, FunctionCallingMode, GenerativeModel, GoogleAIBackend, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, ImagenAspectRatio, ImagenImageFormat, ImagenModel, ImagenPersonFilterLevel, ImagenSafetyFilterLevel, IntegerSchema, Modality, NumberSchema, ObjectSchema, POSSIBLE_ROLES, ResponseModality, Schema, SchemaType, StringSchema, VertexAIBackend, getAI, getGenerativeModel, getImagenModel };
|
|
3061
|
+
export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, FinishReason, FunctionCallingMode, GenerativeModel, GoogleAIBackend, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, ImagenAspectRatio, ImagenImageFormat, ImagenModel, ImagenPersonFilterLevel, ImagenSafetyFilterLevel, InferenceMode, IntegerSchema, Modality, NumberSchema, ObjectSchema, POSSIBLE_ROLES, ResponseModality, Schema, SchemaType, StringSchema, VertexAIBackend, factory, getAI, getGenerativeModel, getImagenModel };
|
|
2702
3062
|
//# sourceMappingURL=index.esm.js.map
|