@firebase/ai 2.0.0 → 2.1.0
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 +178 -8
- package/dist/ai.d.ts +181 -8
- package/dist/esm/index.esm.js +359 -19
- 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/methods/chat-session.d.ts +3 -1
- package/dist/esm/src/methods/chrome-adapter.d.ts +118 -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/generative-model.d.ts +3 -1
- package/dist/esm/src/types/chrome-adapter.d.ts +54 -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 +31 -1
- 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 +359 -18
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.node.cjs.js +359 -18
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +359 -19
- 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/methods/chat-session.d.ts +3 -1
- package/dist/src/methods/chrome-adapter.d.ts +118 -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/generative-model.d.ts +3 -1
- package/dist/src/types/chrome-adapter.d.ts +54 -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 +31 -1
- package/dist/src/types/responses.d.ts +1 -1
- package/dist/src/types/schema.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.node.cjs.js
CHANGED
|
@@ -8,7 +8,7 @@ var util = require('@firebase/util');
|
|
|
8
8
|
var logger$1 = require('@firebase/logger');
|
|
9
9
|
|
|
10
10
|
var name = "@firebase/ai";
|
|
11
|
-
var version = "2.
|
|
11
|
+
var version = "2.1.0";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -33,6 +33,10 @@ const DEFAULT_API_VERSION = 'v1beta';
|
|
|
33
33
|
const PACKAGE_VERSION = version;
|
|
34
34
|
const LANGUAGE_TAG = 'gl-js';
|
|
35
35
|
const DEFAULT_FETCH_TIMEOUT_MS = 180 * 1000;
|
|
36
|
+
/**
|
|
37
|
+
* Defines the name of the default in-cloud model to use for hybrid inference.
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.0-flash-lite';
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* @license
|
|
@@ -88,7 +92,7 @@ const HarmBlockThreshold = {
|
|
|
88
92
|
BLOCK_NONE: 'BLOCK_NONE',
|
|
89
93
|
/**
|
|
90
94
|
* All content will be allowed. This is the same as `BLOCK_NONE`, but the metadata corresponding
|
|
91
|
-
* to the {@link HarmCategory} will not be present in the response.
|
|
95
|
+
* to the {@link (HarmCategory:type)} will not be present in the response.
|
|
92
96
|
*/
|
|
93
97
|
OFF: 'OFF'
|
|
94
98
|
};
|
|
@@ -291,6 +295,16 @@ const ResponseModality = {
|
|
|
291
295
|
*/
|
|
292
296
|
IMAGE: 'IMAGE'
|
|
293
297
|
};
|
|
298
|
+
/**
|
|
299
|
+
* <b>(EXPERIMENTAL)</b>
|
|
300
|
+
* Determines whether inference happens on-device or in-cloud.
|
|
301
|
+
* @public
|
|
302
|
+
*/
|
|
303
|
+
const InferenceMode = {
|
|
304
|
+
'PREFER_ON_DEVICE': 'prefer_on_device',
|
|
305
|
+
'ONLY_ON_DEVICE': 'only_on_device',
|
|
306
|
+
'ONLY_IN_CLOUD': 'only_in_cloud'
|
|
307
|
+
};
|
|
294
308
|
|
|
295
309
|
/**
|
|
296
310
|
* @license
|
|
@@ -464,7 +478,7 @@ const ImagenPersonFilterLevel = {
|
|
|
464
478
|
* To specify an aspect ratio for generated images, set the `aspectRatio` property in your
|
|
465
479
|
* {@link ImagenGenerationConfig}.
|
|
466
480
|
*
|
|
467
|
-
* See the
|
|
481
|
+
* See the {@link http://firebase.google.com/docs/vertex-ai/generate-images | documentation }
|
|
468
482
|
* for more details and examples of the supported aspect ratios.
|
|
469
483
|
*
|
|
470
484
|
* @beta
|
|
@@ -1664,20 +1678,38 @@ function aggregateResponses(responses) {
|
|
|
1664
1678
|
* See the License for the specific language governing permissions and
|
|
1665
1679
|
* limitations under the License.
|
|
1666
1680
|
*/
|
|
1667
|
-
async function
|
|
1681
|
+
async function generateContentStreamOnCloud(apiSettings, model, params, requestOptions) {
|
|
1668
1682
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
1669
1683
|
params = mapGenerateContentRequest(params);
|
|
1670
1684
|
}
|
|
1671
|
-
|
|
1685
|
+
return makeRequest(model, Task.STREAM_GENERATE_CONTENT, apiSettings,
|
|
1672
1686
|
/* stream */ true, JSON.stringify(params), requestOptions);
|
|
1687
|
+
}
|
|
1688
|
+
async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
1689
|
+
let response;
|
|
1690
|
+
if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
|
|
1691
|
+
response = await chromeAdapter.generateContentStream(params);
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
response = await generateContentStreamOnCloud(apiSettings, model, params, requestOptions);
|
|
1695
|
+
}
|
|
1673
1696
|
return processStream(response, apiSettings); // TODO: Map streaming responses
|
|
1674
1697
|
}
|
|
1675
|
-
async function
|
|
1698
|
+
async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
|
|
1676
1699
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
1677
1700
|
params = mapGenerateContentRequest(params);
|
|
1678
1701
|
}
|
|
1679
|
-
|
|
1702
|
+
return makeRequest(model, Task.GENERATE_CONTENT, apiSettings,
|
|
1680
1703
|
/* stream */ false, JSON.stringify(params), requestOptions);
|
|
1704
|
+
}
|
|
1705
|
+
async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
1706
|
+
let response;
|
|
1707
|
+
if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
|
|
1708
|
+
response = await chromeAdapter.generateContent(params);
|
|
1709
|
+
}
|
|
1710
|
+
else {
|
|
1711
|
+
response = await generateContentOnCloud(apiSettings, model, params, requestOptions);
|
|
1712
|
+
}
|
|
1681
1713
|
const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
|
|
1682
1714
|
const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
|
|
1683
1715
|
return {
|
|
@@ -1934,8 +1966,9 @@ const SILENT_ERROR = 'SILENT_ERROR';
|
|
|
1934
1966
|
* @public
|
|
1935
1967
|
*/
|
|
1936
1968
|
class ChatSession {
|
|
1937
|
-
constructor(apiSettings, model, params, requestOptions) {
|
|
1969
|
+
constructor(apiSettings, model, chromeAdapter, params, requestOptions) {
|
|
1938
1970
|
this.model = model;
|
|
1971
|
+
this.chromeAdapter = chromeAdapter;
|
|
1939
1972
|
this.params = params;
|
|
1940
1973
|
this.requestOptions = requestOptions;
|
|
1941
1974
|
this._history = [];
|
|
@@ -1973,7 +2006,7 @@ class ChatSession {
|
|
|
1973
2006
|
let finalResult = {};
|
|
1974
2007
|
// Add onto the chain.
|
|
1975
2008
|
this._sendPromise = this._sendPromise
|
|
1976
|
-
.then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.requestOptions))
|
|
2009
|
+
.then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions))
|
|
1977
2010
|
.then(result => {
|
|
1978
2011
|
if (result.response.candidates &&
|
|
1979
2012
|
result.response.candidates.length > 0) {
|
|
@@ -2012,7 +2045,7 @@ class ChatSession {
|
|
|
2012
2045
|
systemInstruction: this.params?.systemInstruction,
|
|
2013
2046
|
contents: [...this._history, newContent]
|
|
2014
2047
|
};
|
|
2015
|
-
const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.requestOptions);
|
|
2048
|
+
const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions);
|
|
2016
2049
|
// Add onto the chain.
|
|
2017
2050
|
this._sendPromise = this._sendPromise
|
|
2018
2051
|
.then(() => streamPromise)
|
|
@@ -2069,7 +2102,7 @@ class ChatSession {
|
|
|
2069
2102
|
* See the License for the specific language governing permissions and
|
|
2070
2103
|
* limitations under the License.
|
|
2071
2104
|
*/
|
|
2072
|
-
async function
|
|
2105
|
+
async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
|
|
2073
2106
|
let body = '';
|
|
2074
2107
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
2075
2108
|
const mappedParams = mapCountTokensRequest(params, model);
|
|
@@ -2081,6 +2114,12 @@ async function countTokens(apiSettings, model, params, requestOptions) {
|
|
|
2081
2114
|
const response = await makeRequest(model, Task.COUNT_TOKENS, apiSettings, false, body, requestOptions);
|
|
2082
2115
|
return response.json();
|
|
2083
2116
|
}
|
|
2117
|
+
async function countTokens(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
2118
|
+
if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
|
|
2119
|
+
return (await chromeAdapter.countTokens(params)).json();
|
|
2120
|
+
}
|
|
2121
|
+
return countTokensOnCloud(apiSettings, model, params, requestOptions);
|
|
2122
|
+
}
|
|
2084
2123
|
|
|
2085
2124
|
/**
|
|
2086
2125
|
* @license
|
|
@@ -2103,8 +2142,9 @@ async function countTokens(apiSettings, model, params, requestOptions) {
|
|
|
2103
2142
|
* @public
|
|
2104
2143
|
*/
|
|
2105
2144
|
class GenerativeModel extends AIModel {
|
|
2106
|
-
constructor(ai, modelParams, requestOptions) {
|
|
2145
|
+
constructor(ai, modelParams, requestOptions, chromeAdapter) {
|
|
2107
2146
|
super(ai, modelParams.model);
|
|
2147
|
+
this.chromeAdapter = chromeAdapter;
|
|
2108
2148
|
this.generationConfig = modelParams.generationConfig || {};
|
|
2109
2149
|
this.safetySettings = modelParams.safetySettings || [];
|
|
2110
2150
|
this.tools = modelParams.tools;
|
|
@@ -2125,7 +2165,7 @@ class GenerativeModel extends AIModel {
|
|
|
2125
2165
|
toolConfig: this.toolConfig,
|
|
2126
2166
|
systemInstruction: this.systemInstruction,
|
|
2127
2167
|
...formattedParams
|
|
2128
|
-
}, this.requestOptions);
|
|
2168
|
+
}, this.chromeAdapter, this.requestOptions);
|
|
2129
2169
|
}
|
|
2130
2170
|
/**
|
|
2131
2171
|
* Makes a single streaming call to the model
|
|
@@ -2142,14 +2182,14 @@ class GenerativeModel extends AIModel {
|
|
|
2142
2182
|
toolConfig: this.toolConfig,
|
|
2143
2183
|
systemInstruction: this.systemInstruction,
|
|
2144
2184
|
...formattedParams
|
|
2145
|
-
}, this.requestOptions);
|
|
2185
|
+
}, this.chromeAdapter, this.requestOptions);
|
|
2146
2186
|
}
|
|
2147
2187
|
/**
|
|
2148
2188
|
* Gets a new {@link ChatSession} instance which can be used for
|
|
2149
2189
|
* multi-turn chats.
|
|
2150
2190
|
*/
|
|
2151
2191
|
startChat(startChatParams) {
|
|
2152
|
-
return new ChatSession(this._apiSettings, this.model, {
|
|
2192
|
+
return new ChatSession(this._apiSettings, this.model, this.chromeAdapter, {
|
|
2153
2193
|
tools: this.tools,
|
|
2154
2194
|
toolConfig: this.toolConfig,
|
|
2155
2195
|
systemInstruction: this.systemInstruction,
|
|
@@ -2168,7 +2208,7 @@ class GenerativeModel extends AIModel {
|
|
|
2168
2208
|
*/
|
|
2169
2209
|
async countTokens(request) {
|
|
2170
2210
|
const formattedParams = formatGenerateContentInput(request);
|
|
2171
|
-
return countTokens(this._apiSettings, this.model, formattedParams);
|
|
2211
|
+
return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter);
|
|
2172
2212
|
}
|
|
2173
2213
|
}
|
|
2174
2214
|
|
|
@@ -2286,6 +2326,290 @@ class ImagenModel extends AIModel {
|
|
|
2286
2326
|
}
|
|
2287
2327
|
}
|
|
2288
2328
|
|
|
2329
|
+
/**
|
|
2330
|
+
* @internal
|
|
2331
|
+
*/
|
|
2332
|
+
var Availability;
|
|
2333
|
+
(function (Availability) {
|
|
2334
|
+
Availability["UNAVAILABLE"] = "unavailable";
|
|
2335
|
+
Availability["DOWNLOADABLE"] = "downloadable";
|
|
2336
|
+
Availability["DOWNLOADING"] = "downloading";
|
|
2337
|
+
Availability["AVAILABLE"] = "available";
|
|
2338
|
+
})(Availability || (Availability = {}));
|
|
2339
|
+
|
|
2340
|
+
/**
|
|
2341
|
+
* @license
|
|
2342
|
+
* Copyright 2025 Google LLC
|
|
2343
|
+
*
|
|
2344
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2345
|
+
* you may not use this file except in compliance with the License.
|
|
2346
|
+
* You may obtain a copy of the License at
|
|
2347
|
+
*
|
|
2348
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
2349
|
+
*
|
|
2350
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2351
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
2352
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2353
|
+
* See the License for the specific language governing permissions and
|
|
2354
|
+
* limitations under the License.
|
|
2355
|
+
*/
|
|
2356
|
+
/**
|
|
2357
|
+
* Defines an inference "backend" that uses Chrome's on-device model,
|
|
2358
|
+
* and encapsulates logic for detecting when on-device inference is
|
|
2359
|
+
* possible.
|
|
2360
|
+
*/
|
|
2361
|
+
class ChromeAdapterImpl {
|
|
2362
|
+
constructor(languageModelProvider, mode, onDeviceParams = {
|
|
2363
|
+
createOptions: {
|
|
2364
|
+
// Defaults to support image inputs for convenience.
|
|
2365
|
+
expectedInputs: [{ type: 'image' }]
|
|
2366
|
+
}
|
|
2367
|
+
}) {
|
|
2368
|
+
this.languageModelProvider = languageModelProvider;
|
|
2369
|
+
this.mode = mode;
|
|
2370
|
+
this.onDeviceParams = onDeviceParams;
|
|
2371
|
+
this.isDownloading = false;
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Checks if a given request can be made on-device.
|
|
2375
|
+
*
|
|
2376
|
+
* <ol>Encapsulates a few concerns:
|
|
2377
|
+
* <li>the mode</li>
|
|
2378
|
+
* <li>API existence</li>
|
|
2379
|
+
* <li>prompt formatting</li>
|
|
2380
|
+
* <li>model availability, including triggering download if necessary</li>
|
|
2381
|
+
* </ol>
|
|
2382
|
+
*
|
|
2383
|
+
* <p>Pros: callers needn't be concerned with details of on-device availability.</p>
|
|
2384
|
+
* <p>Cons: this method spans a few concerns and splits request validation from usage.
|
|
2385
|
+
* If instance variables weren't already part of the API, we could consider a better
|
|
2386
|
+
* separation of concerns.</p>
|
|
2387
|
+
*/
|
|
2388
|
+
async isAvailable(request) {
|
|
2389
|
+
if (!this.mode) {
|
|
2390
|
+
logger.debug(`On-device inference unavailable because mode is undefined.`);
|
|
2391
|
+
return false;
|
|
2392
|
+
}
|
|
2393
|
+
if (this.mode === InferenceMode.ONLY_IN_CLOUD) {
|
|
2394
|
+
logger.debug(`On-device inference unavailable because mode is "only_in_cloud".`);
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2397
|
+
// Triggers out-of-band download so model will eventually become available.
|
|
2398
|
+
const availability = await this.downloadIfAvailable();
|
|
2399
|
+
if (this.mode === InferenceMode.ONLY_ON_DEVICE) {
|
|
2400
|
+
// If it will never be available due to API inavailability, throw.
|
|
2401
|
+
if (availability === Availability.UNAVAILABLE) {
|
|
2402
|
+
throw new AIError(AIErrorCode.API_NOT_ENABLED, 'Local LanguageModel API not available in this environment.');
|
|
2403
|
+
}
|
|
2404
|
+
else if (availability === Availability.DOWNLOADABLE ||
|
|
2405
|
+
availability === Availability.DOWNLOADING) {
|
|
2406
|
+
// TODO(chholland): Better user experience during download - progress?
|
|
2407
|
+
logger.debug(`Waiting for download of LanguageModel to complete.`);
|
|
2408
|
+
await this.downloadPromise;
|
|
2409
|
+
return true;
|
|
2410
|
+
}
|
|
2411
|
+
return true;
|
|
2412
|
+
}
|
|
2413
|
+
// Applies prefer_on_device logic.
|
|
2414
|
+
if (availability !== Availability.AVAILABLE) {
|
|
2415
|
+
logger.debug(`On-device inference unavailable because availability is "${availability}".`);
|
|
2416
|
+
return false;
|
|
2417
|
+
}
|
|
2418
|
+
if (!ChromeAdapterImpl.isOnDeviceRequest(request)) {
|
|
2419
|
+
logger.debug(`On-device inference unavailable because request is incompatible.`);
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
return true;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Generates content on device.
|
|
2426
|
+
*
|
|
2427
|
+
* <p>This is comparable to {@link GenerativeModel.generateContent} for generating content in
|
|
2428
|
+
* Cloud.</p>
|
|
2429
|
+
* @param request - a standard Firebase AI {@link GenerateContentRequest}
|
|
2430
|
+
* @returns {@link Response}, so we can reuse common response formatting.
|
|
2431
|
+
*/
|
|
2432
|
+
async generateContent(request) {
|
|
2433
|
+
const session = await this.createSession();
|
|
2434
|
+
const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
|
|
2435
|
+
const text = await session.prompt(contents, this.onDeviceParams.promptOptions);
|
|
2436
|
+
return ChromeAdapterImpl.toResponse(text);
|
|
2437
|
+
}
|
|
2438
|
+
/**
|
|
2439
|
+
* Generates content stream on device.
|
|
2440
|
+
*
|
|
2441
|
+
* <p>This is comparable to {@link GenerativeModel.generateContentStream} for generating content in
|
|
2442
|
+
* Cloud.</p>
|
|
2443
|
+
* @param request - a standard Firebase AI {@link GenerateContentRequest}
|
|
2444
|
+
* @returns {@link Response}, so we can reuse common response formatting.
|
|
2445
|
+
*/
|
|
2446
|
+
async generateContentStream(request) {
|
|
2447
|
+
const session = await this.createSession();
|
|
2448
|
+
const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
|
|
2449
|
+
const stream = session.promptStreaming(contents, this.onDeviceParams.promptOptions);
|
|
2450
|
+
return ChromeAdapterImpl.toStreamResponse(stream);
|
|
2451
|
+
}
|
|
2452
|
+
async countTokens(_request) {
|
|
2453
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, 'Count Tokens is not yet available for on-device model.');
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Asserts inference for the given request can be performed by an on-device model.
|
|
2457
|
+
*/
|
|
2458
|
+
static isOnDeviceRequest(request) {
|
|
2459
|
+
// Returns false if the prompt is empty.
|
|
2460
|
+
if (request.contents.length === 0) {
|
|
2461
|
+
logger.debug('Empty prompt rejected for on-device inference.');
|
|
2462
|
+
return false;
|
|
2463
|
+
}
|
|
2464
|
+
for (const content of request.contents) {
|
|
2465
|
+
if (content.role === 'function') {
|
|
2466
|
+
logger.debug(`"Function" role rejected for on-device inference.`);
|
|
2467
|
+
return false;
|
|
2468
|
+
}
|
|
2469
|
+
// Returns false if request contains an image with an unsupported mime type.
|
|
2470
|
+
for (const part of content.parts) {
|
|
2471
|
+
if (part.inlineData &&
|
|
2472
|
+
ChromeAdapterImpl.SUPPORTED_MIME_TYPES.indexOf(part.inlineData.mimeType) === -1) {
|
|
2473
|
+
logger.debug(`Unsupported mime type "${part.inlineData.mimeType}" rejected for on-device inference.`);
|
|
2474
|
+
return false;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
return true;
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Encapsulates logic to get availability and download a model if one is downloadable.
|
|
2482
|
+
*/
|
|
2483
|
+
async downloadIfAvailable() {
|
|
2484
|
+
const availability = await this.languageModelProvider?.availability(this.onDeviceParams.createOptions);
|
|
2485
|
+
if (availability === Availability.DOWNLOADABLE) {
|
|
2486
|
+
this.download();
|
|
2487
|
+
}
|
|
2488
|
+
return availability;
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Triggers out-of-band download of an on-device model.
|
|
2492
|
+
*
|
|
2493
|
+
* <p>Chrome only downloads models as needed. Chrome knows a model is needed when code calls
|
|
2494
|
+
* LanguageModel.create.</p>
|
|
2495
|
+
*
|
|
2496
|
+
* <p>Since Chrome manages the download, the SDK can only avoid redundant download requests by
|
|
2497
|
+
* tracking if a download has previously been requested.</p>
|
|
2498
|
+
*/
|
|
2499
|
+
download() {
|
|
2500
|
+
if (this.isDownloading) {
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
this.isDownloading = true;
|
|
2504
|
+
this.downloadPromise = this.languageModelProvider
|
|
2505
|
+
?.create(this.onDeviceParams.createOptions)
|
|
2506
|
+
.finally(() => {
|
|
2507
|
+
this.isDownloading = false;
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2511
|
+
* Converts Firebase AI {@link Content} object to a Chrome {@link LanguageModelMessage} object.
|
|
2512
|
+
*/
|
|
2513
|
+
static async toLanguageModelMessage(content) {
|
|
2514
|
+
const languageModelMessageContents = await Promise.all(content.parts.map(ChromeAdapterImpl.toLanguageModelMessageContent));
|
|
2515
|
+
return {
|
|
2516
|
+
role: ChromeAdapterImpl.toLanguageModelMessageRole(content.role),
|
|
2517
|
+
content: languageModelMessageContents
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Converts a Firebase AI Part object to a Chrome LanguageModelMessageContent object.
|
|
2522
|
+
*/
|
|
2523
|
+
static async toLanguageModelMessageContent(part) {
|
|
2524
|
+
if (part.text) {
|
|
2525
|
+
return {
|
|
2526
|
+
type: 'text',
|
|
2527
|
+
value: part.text
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
else if (part.inlineData) {
|
|
2531
|
+
const formattedImageContent = await fetch(`data:${part.inlineData.mimeType};base64,${part.inlineData.data}`);
|
|
2532
|
+
const imageBlob = await formattedImageContent.blob();
|
|
2533
|
+
const imageBitmap = await createImageBitmap(imageBlob);
|
|
2534
|
+
return {
|
|
2535
|
+
type: 'image',
|
|
2536
|
+
value: imageBitmap
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, `Processing of this Part type is not currently supported.`);
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Converts a Firebase AI {@link Role} string to a {@link LanguageModelMessageRole} string.
|
|
2543
|
+
*/
|
|
2544
|
+
static toLanguageModelMessageRole(role) {
|
|
2545
|
+
// Assumes 'function' rule has been filtered by isOnDeviceRequest
|
|
2546
|
+
return role === 'model' ? 'assistant' : 'user';
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Abstracts Chrome session creation.
|
|
2550
|
+
*
|
|
2551
|
+
* <p>Chrome uses a multi-turn session for all inference. Firebase AI uses single-turn for all
|
|
2552
|
+
* inference. To map the Firebase AI API to Chrome's API, the SDK creates a new session for all
|
|
2553
|
+
* inference.</p>
|
|
2554
|
+
*
|
|
2555
|
+
* <p>Chrome will remove a model from memory if it's no longer in use, so this method ensures a
|
|
2556
|
+
* new session is created before an old session is destroyed.</p>
|
|
2557
|
+
*/
|
|
2558
|
+
async createSession() {
|
|
2559
|
+
if (!this.languageModelProvider) {
|
|
2560
|
+
throw new AIError(AIErrorCode.UNSUPPORTED, 'Chrome AI requested for unsupported browser version.');
|
|
2561
|
+
}
|
|
2562
|
+
const newSession = await this.languageModelProvider.create(this.onDeviceParams.createOptions);
|
|
2563
|
+
if (this.oldSession) {
|
|
2564
|
+
this.oldSession.destroy();
|
|
2565
|
+
}
|
|
2566
|
+
// Holds session reference, so model isn't unloaded from memory.
|
|
2567
|
+
this.oldSession = newSession;
|
|
2568
|
+
return newSession;
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Formats string returned by Chrome as a {@link Response} returned by Firebase AI.
|
|
2572
|
+
*/
|
|
2573
|
+
static toResponse(text) {
|
|
2574
|
+
return {
|
|
2575
|
+
json: async () => ({
|
|
2576
|
+
candidates: [
|
|
2577
|
+
{
|
|
2578
|
+
content: {
|
|
2579
|
+
parts: [{ text }]
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
]
|
|
2583
|
+
})
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Formats string stream returned by Chrome as SSE returned by Firebase AI.
|
|
2588
|
+
*/
|
|
2589
|
+
static toStreamResponse(stream) {
|
|
2590
|
+
const encoder = new TextEncoder();
|
|
2591
|
+
return {
|
|
2592
|
+
body: stream.pipeThrough(new TransformStream({
|
|
2593
|
+
transform(chunk, controller) {
|
|
2594
|
+
const json = JSON.stringify({
|
|
2595
|
+
candidates: [
|
|
2596
|
+
{
|
|
2597
|
+
content: {
|
|
2598
|
+
role: 'model',
|
|
2599
|
+
parts: [{ text: chunk }]
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
]
|
|
2603
|
+
});
|
|
2604
|
+
controller.enqueue(encoder.encode(`data: ${json}\n\n`));
|
|
2605
|
+
}
|
|
2606
|
+
}))
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
// Visible for testing
|
|
2611
|
+
ChromeAdapterImpl.SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/png'];
|
|
2612
|
+
|
|
2289
2613
|
/**
|
|
2290
2614
|
* @license
|
|
2291
2615
|
* Copyright 2024 Google LLC
|
|
@@ -2653,10 +2977,26 @@ function getAI(app$1 = app.getApp(), options = { backend: new GoogleAIBackend()
|
|
|
2653
2977
|
* @public
|
|
2654
2978
|
*/
|
|
2655
2979
|
function getGenerativeModel(ai, modelParams, requestOptions) {
|
|
2656
|
-
|
|
2980
|
+
// Uses the existence of HybridParams.mode to clarify the type of the modelParams input.
|
|
2981
|
+
const hybridParams = modelParams;
|
|
2982
|
+
let inCloudParams;
|
|
2983
|
+
if (hybridParams.mode) {
|
|
2984
|
+
inCloudParams = hybridParams.inCloudParams || {
|
|
2985
|
+
model: DEFAULT_HYBRID_IN_CLOUD_MODEL
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
else {
|
|
2989
|
+
inCloudParams = modelParams;
|
|
2990
|
+
}
|
|
2991
|
+
if (!inCloudParams.model) {
|
|
2657
2992
|
throw new AIError(AIErrorCode.NO_MODEL, `Must provide a model name. Example: getGenerativeModel({ model: 'my-model-name' })`);
|
|
2658
2993
|
}
|
|
2659
|
-
|
|
2994
|
+
let chromeAdapter;
|
|
2995
|
+
// Do not initialize a ChromeAdapter if we are not in hybrid mode.
|
|
2996
|
+
if (typeof window !== 'undefined' && hybridParams.mode) {
|
|
2997
|
+
chromeAdapter = new ChromeAdapterImpl(window.LanguageModel, hybridParams.mode, hybridParams.onDeviceParams);
|
|
2998
|
+
}
|
|
2999
|
+
return new GenerativeModel(ai, inCloudParams, requestOptions, chromeAdapter);
|
|
2660
3000
|
}
|
|
2661
3001
|
/**
|
|
2662
3002
|
* Returns an {@link ImagenModel} class with methods for using Imagen.
|
|
@@ -2726,6 +3066,7 @@ exports.ImagenImageFormat = ImagenImageFormat;
|
|
|
2726
3066
|
exports.ImagenModel = ImagenModel;
|
|
2727
3067
|
exports.ImagenPersonFilterLevel = ImagenPersonFilterLevel;
|
|
2728
3068
|
exports.ImagenSafetyFilterLevel = ImagenSafetyFilterLevel;
|
|
3069
|
+
exports.InferenceMode = InferenceMode;
|
|
2729
3070
|
exports.IntegerSchema = IntegerSchema;
|
|
2730
3071
|
exports.Modality = Modality;
|
|
2731
3072
|
exports.NumberSchema = NumberSchema;
|