1509 lines
60 KiB
JavaScript
1509 lines
60 KiB
JavaScript
|
import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app';
|
||
|
import { Component } from '@firebase/component';
|
||
|
import { FirebaseError, getModularInstance } from '@firebase/util';
|
||
|
import { __asyncGenerator, __await } from 'tslib';
|
||
|
|
||
|
var name = "@firebase/vertexai";
|
||
|
var version = "1.0.0";
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
const VERTEX_TYPE = 'vertexAI';
|
||
|
const DEFAULT_LOCATION = 'us-central1';
|
||
|
const DEFAULT_BASE_URL = 'https://firebasevertexai.googleapis.com';
|
||
|
const DEFAULT_API_VERSION = 'v1beta';
|
||
|
const PACKAGE_VERSION = version;
|
||
|
const LANGUAGE_TAG = 'gl-js';
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
class VertexAIService {
|
||
|
constructor(app, authProvider, appCheckProvider, options) {
|
||
|
var _a;
|
||
|
this.app = app;
|
||
|
this.options = options;
|
||
|
const appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
|
||
|
const auth = authProvider === null || authProvider === void 0 ? void 0 : authProvider.getImmediate({ optional: true });
|
||
|
this.auth = auth || null;
|
||
|
this.appCheck = appCheck || null;
|
||
|
this.location = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.location) || DEFAULT_LOCATION;
|
||
|
}
|
||
|
_delete() {
|
||
|
return Promise.resolve();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Error class for the Vertex AI in Firebase SDK.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class VertexAIError extends FirebaseError {
|
||
|
/**
|
||
|
* Constructs a new instance of the `VertexAIError` class.
|
||
|
*
|
||
|
* @param code - The error code from <code>{@link VertexAIErrorCode}</code>.
|
||
|
* @param message - A human-readable message describing the error.
|
||
|
* @param customErrorData - Optional error data.
|
||
|
*/
|
||
|
constructor(code, message, customErrorData) {
|
||
|
// Match error format used by FirebaseError from ErrorFactory
|
||
|
const service = VERTEX_TYPE;
|
||
|
const serviceName = 'VertexAI';
|
||
|
const fullCode = `${service}/${code}`;
|
||
|
const fullMessage = `${serviceName}: ${message} (${fullCode})`;
|
||
|
super(code, fullMessage);
|
||
|
this.code = code;
|
||
|
this.customErrorData = customErrorData;
|
||
|
// FirebaseError initializes a stack trace, but it assumes the error is created from the error
|
||
|
// factory. Since we break this assumption, we set the stack trace to be originating from this
|
||
|
// constructor.
|
||
|
// This is only supported in V8.
|
||
|
if (Error.captureStackTrace) {
|
||
|
// Allows us to initialize the stack trace without including the constructor itself at the
|
||
|
// top level of the stack trace.
|
||
|
Error.captureStackTrace(this, VertexAIError);
|
||
|
}
|
||
|
// Allows instanceof VertexAIError in ES5/ES6
|
||
|
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||
|
// TODO(dlarocque): Replace this with `new.target`: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
||
|
// which we can now use since we no longer target ES5.
|
||
|
Object.setPrototypeOf(this, VertexAIError.prototype);
|
||
|
// Since Error is an interface, we don't inherit toString and so we define it ourselves.
|
||
|
this.toString = () => fullMessage;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
var Task;
|
||
|
(function (Task) {
|
||
|
Task["GENERATE_CONTENT"] = "generateContent";
|
||
|
Task["STREAM_GENERATE_CONTENT"] = "streamGenerateContent";
|
||
|
Task["COUNT_TOKENS"] = "countTokens";
|
||
|
})(Task || (Task = {}));
|
||
|
class RequestUrl {
|
||
|
constructor(model, task, apiSettings, stream, requestOptions) {
|
||
|
this.model = model;
|
||
|
this.task = task;
|
||
|
this.apiSettings = apiSettings;
|
||
|
this.stream = stream;
|
||
|
this.requestOptions = requestOptions;
|
||
|
}
|
||
|
toString() {
|
||
|
var _a;
|
||
|
// TODO: allow user-set option if that feature becomes available
|
||
|
const apiVersion = DEFAULT_API_VERSION;
|
||
|
const baseUrl = ((_a = this.requestOptions) === null || _a === void 0 ? void 0 : _a.baseUrl) || DEFAULT_BASE_URL;
|
||
|
let url = `${baseUrl}/${apiVersion}`;
|
||
|
url += `/projects/${this.apiSettings.project}`;
|
||
|
url += `/locations/${this.apiSettings.location}`;
|
||
|
url += `/${this.model}`;
|
||
|
url += `:${this.task}`;
|
||
|
if (this.stream) {
|
||
|
url += '?alt=sse';
|
||
|
}
|
||
|
return url;
|
||
|
}
|
||
|
/**
|
||
|
* If the model needs to be passed to the backend, it needs to
|
||
|
* include project and location path.
|
||
|
*/
|
||
|
get fullModelString() {
|
||
|
let modelString = `projects/${this.apiSettings.project}`;
|
||
|
modelString += `/locations/${this.apiSettings.location}`;
|
||
|
modelString += `/${this.model}`;
|
||
|
return modelString;
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Log language and "fire/version" to x-goog-api-client
|
||
|
*/
|
||
|
function getClientHeaders() {
|
||
|
const loggingTags = [];
|
||
|
loggingTags.push(`${LANGUAGE_TAG}/${PACKAGE_VERSION}`);
|
||
|
loggingTags.push(`fire/${PACKAGE_VERSION}`);
|
||
|
return loggingTags.join(' ');
|
||
|
}
|
||
|
async function getHeaders(url) {
|
||
|
const headers = new Headers();
|
||
|
headers.append('Content-Type', 'application/json');
|
||
|
headers.append('x-goog-api-client', getClientHeaders());
|
||
|
headers.append('x-goog-api-key', url.apiSettings.apiKey);
|
||
|
if (url.apiSettings.getAppCheckToken) {
|
||
|
const appCheckToken = await url.apiSettings.getAppCheckToken();
|
||
|
if (appCheckToken && !appCheckToken.error) {
|
||
|
headers.append('X-Firebase-AppCheck', appCheckToken.token);
|
||
|
}
|
||
|
}
|
||
|
if (url.apiSettings.getAuthToken) {
|
||
|
const authToken = await url.apiSettings.getAuthToken();
|
||
|
if (authToken) {
|
||
|
headers.append('Authorization', `Firebase ${authToken.accessToken}`);
|
||
|
}
|
||
|
}
|
||
|
return headers;
|
||
|
}
|
||
|
async function constructRequest(model, task, apiSettings, stream, body, requestOptions) {
|
||
|
const url = new RequestUrl(model, task, apiSettings, stream, requestOptions);
|
||
|
return {
|
||
|
url: url.toString(),
|
||
|
fetchOptions: Object.assign(Object.assign({}, buildFetchOptions(requestOptions)), { method: 'POST', headers: await getHeaders(url), body })
|
||
|
};
|
||
|
}
|
||
|
async function makeRequest(model, task, apiSettings, stream, body, requestOptions) {
|
||
|
const url = new RequestUrl(model, task, apiSettings, stream, requestOptions);
|
||
|
let response;
|
||
|
try {
|
||
|
const request = await constructRequest(model, task, apiSettings, stream, body, requestOptions);
|
||
|
response = await fetch(request.url, request.fetchOptions);
|
||
|
if (!response.ok) {
|
||
|
let message = '';
|
||
|
let errorDetails;
|
||
|
try {
|
||
|
const json = await response.json();
|
||
|
message = json.error.message;
|
||
|
if (json.error.details) {
|
||
|
message += ` ${JSON.stringify(json.error.details)}`;
|
||
|
errorDetails = json.error.details;
|
||
|
}
|
||
|
}
|
||
|
catch (e) {
|
||
|
// ignored
|
||
|
}
|
||
|
if (response.status === 403 &&
|
||
|
errorDetails.some((detail) => detail.reason === 'SERVICE_DISABLED') &&
|
||
|
errorDetails.some((detail) => {
|
||
|
var _a, _b;
|
||
|
return (_b = (_a = detail.links) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.description.includes('Google developers console API activation');
|
||
|
})) {
|
||
|
throw new VertexAIError("api-not-enabled" /* VertexAIErrorCode.API_NOT_ENABLED */, `The Vertex AI in Firebase SDK requires the Vertex AI in Firebase
|
||
|
API ('firebasevertexai.googleapis.com') to be enabled in your
|
||
|
Firebase project. Enable this API by visiting the Firebase Console
|
||
|
at https://console.firebase.google.com/project/${url.apiSettings.project}/genai/
|
||
|
and clicking "Get started". If you enabled this API recently,
|
||
|
wait a few minutes for the action to propagate to our systems and
|
||
|
then retry.`, {
|
||
|
status: response.status,
|
||
|
statusText: response.statusText,
|
||
|
errorDetails
|
||
|
});
|
||
|
}
|
||
|
throw new VertexAIError("fetch-error" /* VertexAIErrorCode.FETCH_ERROR */, `Error fetching from ${url}: [${response.status} ${response.statusText}] ${message}`, {
|
||
|
status: response.status,
|
||
|
statusText: response.statusText,
|
||
|
errorDetails
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
catch (e) {
|
||
|
let err = e;
|
||
|
if (e.code !== "fetch-error" /* VertexAIErrorCode.FETCH_ERROR */ &&
|
||
|
e.code !== "api-not-enabled" /* VertexAIErrorCode.API_NOT_ENABLED */ &&
|
||
|
e instanceof Error) {
|
||
|
err = new VertexAIError("error" /* VertexAIErrorCode.ERROR */, `Error fetching from ${url.toString()}: ${e.message}`);
|
||
|
err.stack = e.stack;
|
||
|
}
|
||
|
throw err;
|
||
|
}
|
||
|
return response;
|
||
|
}
|
||
|
/**
|
||
|
* Generates the request options to be passed to the fetch API.
|
||
|
* @param requestOptions - The user-defined request options.
|
||
|
* @returns The generated request options.
|
||
|
*/
|
||
|
function buildFetchOptions(requestOptions) {
|
||
|
const fetchOptions = {};
|
||
|
let timeoutMillis = 180 * 1000; // default: 180 s
|
||
|
if ((requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.timeout) && (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.timeout) >= 0) {
|
||
|
timeoutMillis = requestOptions.timeout;
|
||
|
}
|
||
|
const abortController = new AbortController();
|
||
|
const signal = abortController.signal;
|
||
|
setTimeout(() => abortController.abort(), timeoutMillis);
|
||
|
fetchOptions.signal = signal;
|
||
|
return fetchOptions;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Possible roles.
|
||
|
* @public
|
||
|
*/
|
||
|
const POSSIBLE_ROLES = ['user', 'model', 'function', 'system'];
|
||
|
/**
|
||
|
* Harm categories that would cause prompts or candidates to be blocked.
|
||
|
* @public
|
||
|
*/
|
||
|
var HarmCategory;
|
||
|
(function (HarmCategory) {
|
||
|
HarmCategory["HARM_CATEGORY_HATE_SPEECH"] = "HARM_CATEGORY_HATE_SPEECH";
|
||
|
HarmCategory["HARM_CATEGORY_SEXUALLY_EXPLICIT"] = "HARM_CATEGORY_SEXUALLY_EXPLICIT";
|
||
|
HarmCategory["HARM_CATEGORY_HARASSMENT"] = "HARM_CATEGORY_HARASSMENT";
|
||
|
HarmCategory["HARM_CATEGORY_DANGEROUS_CONTENT"] = "HARM_CATEGORY_DANGEROUS_CONTENT";
|
||
|
})(HarmCategory || (HarmCategory = {}));
|
||
|
/**
|
||
|
* Threshold above which a prompt or candidate will be blocked.
|
||
|
* @public
|
||
|
*/
|
||
|
var HarmBlockThreshold;
|
||
|
(function (HarmBlockThreshold) {
|
||
|
// Content with NEGLIGIBLE will be allowed.
|
||
|
HarmBlockThreshold["BLOCK_LOW_AND_ABOVE"] = "BLOCK_LOW_AND_ABOVE";
|
||
|
// Content with NEGLIGIBLE and LOW will be allowed.
|
||
|
HarmBlockThreshold["BLOCK_MEDIUM_AND_ABOVE"] = "BLOCK_MEDIUM_AND_ABOVE";
|
||
|
// Content with NEGLIGIBLE, LOW, and MEDIUM will be allowed.
|
||
|
HarmBlockThreshold["BLOCK_ONLY_HIGH"] = "BLOCK_ONLY_HIGH";
|
||
|
// All content will be allowed.
|
||
|
HarmBlockThreshold["BLOCK_NONE"] = "BLOCK_NONE";
|
||
|
})(HarmBlockThreshold || (HarmBlockThreshold = {}));
|
||
|
/**
|
||
|
* @public
|
||
|
*/
|
||
|
var HarmBlockMethod;
|
||
|
(function (HarmBlockMethod) {
|
||
|
// The harm block method uses both probability and severity scores.
|
||
|
HarmBlockMethod["SEVERITY"] = "SEVERITY";
|
||
|
// The harm block method uses the probability score.
|
||
|
HarmBlockMethod["PROBABILITY"] = "PROBABILITY";
|
||
|
})(HarmBlockMethod || (HarmBlockMethod = {}));
|
||
|
/**
|
||
|
* Probability that a prompt or candidate matches a harm category.
|
||
|
* @public
|
||
|
*/
|
||
|
var HarmProbability;
|
||
|
(function (HarmProbability) {
|
||
|
// Content has a negligible chance of being unsafe.
|
||
|
HarmProbability["NEGLIGIBLE"] = "NEGLIGIBLE";
|
||
|
// Content has a low chance of being unsafe.
|
||
|
HarmProbability["LOW"] = "LOW";
|
||
|
// Content has a medium chance of being unsafe.
|
||
|
HarmProbability["MEDIUM"] = "MEDIUM";
|
||
|
// Content has a high chance of being unsafe.
|
||
|
HarmProbability["HIGH"] = "HIGH";
|
||
|
})(HarmProbability || (HarmProbability = {}));
|
||
|
/**
|
||
|
* Harm severity levels.
|
||
|
* @public
|
||
|
*/
|
||
|
var HarmSeverity;
|
||
|
(function (HarmSeverity) {
|
||
|
// Negligible level of harm severity.
|
||
|
HarmSeverity["HARM_SEVERITY_NEGLIGIBLE"] = "HARM_SEVERITY_NEGLIGIBLE";
|
||
|
// Low level of harm severity.
|
||
|
HarmSeverity["HARM_SEVERITY_LOW"] = "HARM_SEVERITY_LOW";
|
||
|
// Medium level of harm severity.
|
||
|
HarmSeverity["HARM_SEVERITY_MEDIUM"] = "HARM_SEVERITY_MEDIUM";
|
||
|
// High level of harm severity.
|
||
|
HarmSeverity["HARM_SEVERITY_HIGH"] = "HARM_SEVERITY_HIGH";
|
||
|
})(HarmSeverity || (HarmSeverity = {}));
|
||
|
/**
|
||
|
* Reason that a prompt was blocked.
|
||
|
* @public
|
||
|
*/
|
||
|
var BlockReason;
|
||
|
(function (BlockReason) {
|
||
|
// Content was blocked by safety settings.
|
||
|
BlockReason["SAFETY"] = "SAFETY";
|
||
|
// Content was blocked, but the reason is uncategorized.
|
||
|
BlockReason["OTHER"] = "OTHER";
|
||
|
})(BlockReason || (BlockReason = {}));
|
||
|
/**
|
||
|
* Reason that a candidate finished.
|
||
|
* @public
|
||
|
*/
|
||
|
var FinishReason;
|
||
|
(function (FinishReason) {
|
||
|
// Natural stop point of the model or provided stop sequence.
|
||
|
FinishReason["STOP"] = "STOP";
|
||
|
// The maximum number of tokens as specified in the request was reached.
|
||
|
FinishReason["MAX_TOKENS"] = "MAX_TOKENS";
|
||
|
// The candidate content was flagged for safety reasons.
|
||
|
FinishReason["SAFETY"] = "SAFETY";
|
||
|
// The candidate content was flagged for recitation reasons.
|
||
|
FinishReason["RECITATION"] = "RECITATION";
|
||
|
// Unknown reason.
|
||
|
FinishReason["OTHER"] = "OTHER";
|
||
|
})(FinishReason || (FinishReason = {}));
|
||
|
/**
|
||
|
* @public
|
||
|
*/
|
||
|
var FunctionCallingMode;
|
||
|
(function (FunctionCallingMode) {
|
||
|
// Default model behavior, model decides to predict either a function call
|
||
|
// or a natural language response.
|
||
|
FunctionCallingMode["AUTO"] = "AUTO";
|
||
|
// Model is constrained to always predicting a function call only.
|
||
|
// If "allowed_function_names" is set, the predicted function call will be
|
||
|
// limited to any one of "allowed_function_names", else the predicted
|
||
|
// function call will be any one of the provided "function_declarations".
|
||
|
FunctionCallingMode["ANY"] = "ANY";
|
||
|
// Model will not predict any function call. Model behavior is same as when
|
||
|
// not passing any function declarations.
|
||
|
FunctionCallingMode["NONE"] = "NONE";
|
||
|
})(FunctionCallingMode || (FunctionCallingMode = {}));
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Contains the list of OpenAPI data types
|
||
|
* as defined by the
|
||
|
* {@link https://swagger.io/docs/specification/data-models/data-types/ | OpenAPI specification}
|
||
|
* @public
|
||
|
*/
|
||
|
var SchemaType;
|
||
|
(function (SchemaType) {
|
||
|
/** String type. */
|
||
|
SchemaType["STRING"] = "string";
|
||
|
/** Number type. */
|
||
|
SchemaType["NUMBER"] = "number";
|
||
|
/** Integer type. */
|
||
|
SchemaType["INTEGER"] = "integer";
|
||
|
/** Boolean type. */
|
||
|
SchemaType["BOOLEAN"] = "boolean";
|
||
|
/** Array type. */
|
||
|
SchemaType["ARRAY"] = "array";
|
||
|
/** Object type. */
|
||
|
SchemaType["OBJECT"] = "object";
|
||
|
})(SchemaType || (SchemaType = {}));
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Creates an EnhancedGenerateContentResponse object that has helper functions and
|
||
|
* other modifications that improve usability.
|
||
|
*/
|
||
|
function createEnhancedContentResponse(response) {
|
||
|
/**
|
||
|
* The Vertex AI backend omits default values.
|
||
|
* This causes the `index` property to be omitted from the first candidate in the
|
||
|
* response, since it has index 0, and 0 is a default value.
|
||
|
* See: https://github.com/firebase/firebase-js-sdk/issues/8566
|
||
|
*/
|
||
|
if (response.candidates && !response.candidates[0].hasOwnProperty('index')) {
|
||
|
response.candidates[0].index = 0;
|
||
|
}
|
||
|
const responseWithHelpers = addHelpers(response);
|
||
|
return responseWithHelpers;
|
||
|
}
|
||
|
/**
|
||
|
* Adds convenience helper methods to a response object, including stream
|
||
|
* chunks (as long as each chunk is a complete GenerateContentResponse JSON).
|
||
|
*/
|
||
|
function addHelpers(response) {
|
||
|
response.text = () => {
|
||
|
if (response.candidates && response.candidates.length > 0) {
|
||
|
if (response.candidates.length > 1) {
|
||
|
console.warn(`This response had ${response.candidates.length} ` +
|
||
|
`candidates. Returning text from the first candidate only. ` +
|
||
|
`Access response.candidates directly to use the other candidates.`);
|
||
|
}
|
||
|
if (hadBadFinishReason(response.candidates[0])) {
|
||
|
throw new VertexAIError("response-error" /* VertexAIErrorCode.RESPONSE_ERROR */, `Response error: ${formatBlockErrorMessage(response)}. Response body stored in error.response`, {
|
||
|
response
|
||
|
});
|
||
|
}
|
||
|
return getText(response);
|
||
|
}
|
||
|
else if (response.promptFeedback) {
|
||
|
throw new VertexAIError("response-error" /* VertexAIErrorCode.RESPONSE_ERROR */, `Text not available. ${formatBlockErrorMessage(response)}`, {
|
||
|
response
|
||
|
});
|
||
|
}
|
||
|
return '';
|
||
|
};
|
||
|
response.functionCalls = () => {
|
||
|
if (response.candidates && response.candidates.length > 0) {
|
||
|
if (response.candidates.length > 1) {
|
||
|
console.warn(`This response had ${response.candidates.length} ` +
|
||
|
`candidates. Returning function calls from the first candidate only. ` +
|
||
|
`Access response.candidates directly to use the other candidates.`);
|
||
|
}
|
||
|
if (hadBadFinishReason(response.candidates[0])) {
|
||
|
throw new VertexAIError("response-error" /* VertexAIErrorCode.RESPONSE_ERROR */, `Response error: ${formatBlockErrorMessage(response)}. Response body stored in error.response`, {
|
||
|
response
|
||
|
});
|
||
|
}
|
||
|
return getFunctionCalls(response);
|
||
|
}
|
||
|
else if (response.promptFeedback) {
|
||
|
throw new VertexAIError("response-error" /* VertexAIErrorCode.RESPONSE_ERROR */, `Function call not available. ${formatBlockErrorMessage(response)}`, {
|
||
|
response
|
||
|
});
|
||
|
}
|
||
|
return undefined;
|
||
|
};
|
||
|
return response;
|
||
|
}
|
||
|
/**
|
||
|
* Returns all text found in all parts of first candidate.
|
||
|
*/
|
||
|
function getText(response) {
|
||
|
var _a, _b, _c, _d;
|
||
|
const textStrings = [];
|
||
|
if ((_b = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0].content) === null || _b === void 0 ? void 0 : _b.parts) {
|
||
|
for (const part of (_d = (_c = response.candidates) === null || _c === void 0 ? void 0 : _c[0].content) === null || _d === void 0 ? void 0 : _d.parts) {
|
||
|
if (part.text) {
|
||
|
textStrings.push(part.text);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (textStrings.length > 0) {
|
||
|
return textStrings.join('');
|
||
|
}
|
||
|
else {
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Returns <code>{@link FunctionCall}</code>s associated with first candidate.
|
||
|
*/
|
||
|
function getFunctionCalls(response) {
|
||
|
var _a, _b, _c, _d;
|
||
|
const functionCalls = [];
|
||
|
if ((_b = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0].content) === null || _b === void 0 ? void 0 : _b.parts) {
|
||
|
for (const part of (_d = (_c = response.candidates) === null || _c === void 0 ? void 0 : _c[0].content) === null || _d === void 0 ? void 0 : _d.parts) {
|
||
|
if (part.functionCall) {
|
||
|
functionCalls.push(part.functionCall);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (functionCalls.length > 0) {
|
||
|
return functionCalls;
|
||
|
}
|
||
|
else {
|
||
|
return undefined;
|
||
|
}
|
||
|
}
|
||
|
const badFinishReasons = [FinishReason.RECITATION, FinishReason.SAFETY];
|
||
|
function hadBadFinishReason(candidate) {
|
||
|
return (!!candidate.finishReason &&
|
||
|
badFinishReasons.includes(candidate.finishReason));
|
||
|
}
|
||
|
function formatBlockErrorMessage(response) {
|
||
|
var _a, _b, _c;
|
||
|
let message = '';
|
||
|
if ((!response.candidates || response.candidates.length === 0) &&
|
||
|
response.promptFeedback) {
|
||
|
message += 'Response was blocked';
|
||
|
if ((_a = response.promptFeedback) === null || _a === void 0 ? void 0 : _a.blockReason) {
|
||
|
message += ` due to ${response.promptFeedback.blockReason}`;
|
||
|
}
|
||
|
if ((_b = response.promptFeedback) === null || _b === void 0 ? void 0 : _b.blockReasonMessage) {
|
||
|
message += `: ${response.promptFeedback.blockReasonMessage}`;
|
||
|
}
|
||
|
}
|
||
|
else if ((_c = response.candidates) === null || _c === void 0 ? void 0 : _c[0]) {
|
||
|
const firstCandidate = response.candidates[0];
|
||
|
if (hadBadFinishReason(firstCandidate)) {
|
||
|
message += `Candidate was blocked due to ${firstCandidate.finishReason}`;
|
||
|
if (firstCandidate.finishMessage) {
|
||
|
message += `: ${firstCandidate.finishMessage}`;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return message;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
|
||
|
/**
|
||
|
* Process a response.body stream from the backend and return an
|
||
|
* iterator that provides one complete GenerateContentResponse at a time
|
||
|
* and a promise that resolves with a single aggregated
|
||
|
* GenerateContentResponse.
|
||
|
*
|
||
|
* @param response - Response from a fetch call
|
||
|
*/
|
||
|
function processStream(response) {
|
||
|
const inputStream = response.body.pipeThrough(new TextDecoderStream('utf8', { fatal: true }));
|
||
|
const responseStream = getResponseStream(inputStream);
|
||
|
const [stream1, stream2] = responseStream.tee();
|
||
|
return {
|
||
|
stream: generateResponseSequence(stream1),
|
||
|
response: getResponsePromise(stream2)
|
||
|
};
|
||
|
}
|
||
|
async function getResponsePromise(stream) {
|
||
|
const allResponses = [];
|
||
|
const reader = stream.getReader();
|
||
|
while (true) {
|
||
|
const { done, value } = await reader.read();
|
||
|
if (done) {
|
||
|
const enhancedResponse = createEnhancedContentResponse(aggregateResponses(allResponses));
|
||
|
return enhancedResponse;
|
||
|
}
|
||
|
allResponses.push(value);
|
||
|
}
|
||
|
}
|
||
|
function generateResponseSequence(stream) {
|
||
|
return __asyncGenerator(this, arguments, function* generateResponseSequence_1() {
|
||
|
const reader = stream.getReader();
|
||
|
while (true) {
|
||
|
const { value, done } = yield __await(reader.read());
|
||
|
if (done) {
|
||
|
break;
|
||
|
}
|
||
|
const enhancedResponse = createEnhancedContentResponse(value);
|
||
|
yield yield __await(enhancedResponse);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Reads a raw stream from the fetch response and join incomplete
|
||
|
* chunks, returning a new stream that provides a single complete
|
||
|
* GenerateContentResponse in each iteration.
|
||
|
*/
|
||
|
function getResponseStream(inputStream) {
|
||
|
const reader = inputStream.getReader();
|
||
|
const stream = new ReadableStream({
|
||
|
start(controller) {
|
||
|
let currentText = '';
|
||
|
return pump();
|
||
|
function pump() {
|
||
|
return reader.read().then(({ value, done }) => {
|
||
|
if (done) {
|
||
|
if (currentText.trim()) {
|
||
|
controller.error(new VertexAIError("parse-failed" /* VertexAIErrorCode.PARSE_FAILED */, 'Failed to parse stream'));
|
||
|
return;
|
||
|
}
|
||
|
controller.close();
|
||
|
return;
|
||
|
}
|
||
|
currentText += value;
|
||
|
let match = currentText.match(responseLineRE);
|
||
|
let parsedResponse;
|
||
|
while (match) {
|
||
|
try {
|
||
|
parsedResponse = JSON.parse(match[1]);
|
||
|
}
|
||
|
catch (e) {
|
||
|
controller.error(new VertexAIError("parse-failed" /* VertexAIErrorCode.PARSE_FAILED */, `Error parsing JSON response: "${match[1]}`));
|
||
|
return;
|
||
|
}
|
||
|
controller.enqueue(parsedResponse);
|
||
|
currentText = currentText.substring(match[0].length);
|
||
|
match = currentText.match(responseLineRE);
|
||
|
}
|
||
|
return pump();
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return stream;
|
||
|
}
|
||
|
/**
|
||
|
* Aggregates an array of `GenerateContentResponse`s into a single
|
||
|
* GenerateContentResponse.
|
||
|
*/
|
||
|
function aggregateResponses(responses) {
|
||
|
const lastResponse = responses[responses.length - 1];
|
||
|
const aggregatedResponse = {
|
||
|
promptFeedback: lastResponse === null || lastResponse === void 0 ? void 0 : lastResponse.promptFeedback
|
||
|
};
|
||
|
for (const response of responses) {
|
||
|
if (response.candidates) {
|
||
|
for (const candidate of response.candidates) {
|
||
|
// Index will be undefined if it's the first index (0), so we should use 0 if it's undefined.
|
||
|
// See: https://github.com/firebase/firebase-js-sdk/issues/8566
|
||
|
const i = candidate.index || 0;
|
||
|
if (!aggregatedResponse.candidates) {
|
||
|
aggregatedResponse.candidates = [];
|
||
|
}
|
||
|
if (!aggregatedResponse.candidates[i]) {
|
||
|
aggregatedResponse.candidates[i] = {
|
||
|
index: candidate.index
|
||
|
};
|
||
|
}
|
||
|
// Keep overwriting, the last one will be final
|
||
|
aggregatedResponse.candidates[i].citationMetadata =
|
||
|
candidate.citationMetadata;
|
||
|
aggregatedResponse.candidates[i].finishReason = candidate.finishReason;
|
||
|
aggregatedResponse.candidates[i].finishMessage =
|
||
|
candidate.finishMessage;
|
||
|
aggregatedResponse.candidates[i].safetyRatings =
|
||
|
candidate.safetyRatings;
|
||
|
/**
|
||
|
* Candidates should always have content and parts, but this handles
|
||
|
* possible malformed responses.
|
||
|
*/
|
||
|
if (candidate.content && candidate.content.parts) {
|
||
|
if (!aggregatedResponse.candidates[i].content) {
|
||
|
aggregatedResponse.candidates[i].content = {
|
||
|
role: candidate.content.role || 'user',
|
||
|
parts: []
|
||
|
};
|
||
|
}
|
||
|
const newPart = {};
|
||
|
for (const part of candidate.content.parts) {
|
||
|
if (part.text) {
|
||
|
newPart.text = part.text;
|
||
|
}
|
||
|
if (part.functionCall) {
|
||
|
newPart.functionCall = part.functionCall;
|
||
|
}
|
||
|
if (Object.keys(newPart).length === 0) {
|
||
|
newPart.text = '';
|
||
|
}
|
||
|
aggregatedResponse.candidates[i].content.parts.push(newPart);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return aggregatedResponse;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
async function generateContentStream(apiSettings, model, params, requestOptions) {
|
||
|
const response = await makeRequest(model, Task.STREAM_GENERATE_CONTENT, apiSettings,
|
||
|
/* stream */ true, JSON.stringify(params), requestOptions);
|
||
|
return processStream(response);
|
||
|
}
|
||
|
async function generateContent(apiSettings, model, params, requestOptions) {
|
||
|
const response = await makeRequest(model, Task.GENERATE_CONTENT, apiSettings,
|
||
|
/* stream */ false, JSON.stringify(params), requestOptions);
|
||
|
const responseJson = await response.json();
|
||
|
const enhancedResponse = createEnhancedContentResponse(responseJson);
|
||
|
return {
|
||
|
response: enhancedResponse
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
function formatSystemInstruction(input) {
|
||
|
// null or undefined
|
||
|
if (input == null) {
|
||
|
return undefined;
|
||
|
}
|
||
|
else if (typeof input === 'string') {
|
||
|
return { role: 'system', parts: [{ text: input }] };
|
||
|
}
|
||
|
else if (input.text) {
|
||
|
return { role: 'system', parts: [input] };
|
||
|
}
|
||
|
else if (input.parts) {
|
||
|
if (!input.role) {
|
||
|
return { role: 'system', parts: input.parts };
|
||
|
}
|
||
|
else {
|
||
|
return input;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
function formatNewContent(request) {
|
||
|
let newParts = [];
|
||
|
if (typeof request === 'string') {
|
||
|
newParts = [{ text: request }];
|
||
|
}
|
||
|
else {
|
||
|
for (const partOrString of request) {
|
||
|
if (typeof partOrString === 'string') {
|
||
|
newParts.push({ text: partOrString });
|
||
|
}
|
||
|
else {
|
||
|
newParts.push(partOrString);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return assignRoleToPartsAndValidateSendMessageRequest(newParts);
|
||
|
}
|
||
|
/**
|
||
|
* When multiple Part types (i.e. FunctionResponsePart and TextPart) are
|
||
|
* passed in a single Part array, we may need to assign different roles to each
|
||
|
* part. Currently only FunctionResponsePart requires a role other than 'user'.
|
||
|
* @private
|
||
|
* @param parts Array of parts to pass to the model
|
||
|
* @returns Array of content items
|
||
|
*/
|
||
|
function assignRoleToPartsAndValidateSendMessageRequest(parts) {
|
||
|
const userContent = { role: 'user', parts: [] };
|
||
|
const functionContent = { role: 'function', parts: [] };
|
||
|
let hasUserContent = false;
|
||
|
let hasFunctionContent = false;
|
||
|
for (const part of parts) {
|
||
|
if ('functionResponse' in part) {
|
||
|
functionContent.parts.push(part);
|
||
|
hasFunctionContent = true;
|
||
|
}
|
||
|
else {
|
||
|
userContent.parts.push(part);
|
||
|
hasUserContent = true;
|
||
|
}
|
||
|
}
|
||
|
if (hasUserContent && hasFunctionContent) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, 'Within a single message, FunctionResponse cannot be mixed with other type of Part in the request for sending chat message.');
|
||
|
}
|
||
|
if (!hasUserContent && !hasFunctionContent) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, 'No Content is provided for sending chat message.');
|
||
|
}
|
||
|
if (hasUserContent) {
|
||
|
return userContent;
|
||
|
}
|
||
|
return functionContent;
|
||
|
}
|
||
|
function formatGenerateContentInput(params) {
|
||
|
let formattedRequest;
|
||
|
if (params.contents) {
|
||
|
formattedRequest = params;
|
||
|
}
|
||
|
else {
|
||
|
// Array or string
|
||
|
const content = formatNewContent(params);
|
||
|
formattedRequest = { contents: [content] };
|
||
|
}
|
||
|
if (params.systemInstruction) {
|
||
|
formattedRequest.systemInstruction = formatSystemInstruction(params.systemInstruction);
|
||
|
}
|
||
|
return formattedRequest;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
// https://ai.google.dev/api/rest/v1beta/Content#part
|
||
|
const VALID_PART_FIELDS = [
|
||
|
'text',
|
||
|
'inlineData',
|
||
|
'functionCall',
|
||
|
'functionResponse'
|
||
|
];
|
||
|
const VALID_PARTS_PER_ROLE = {
|
||
|
user: ['text', 'inlineData'],
|
||
|
function: ['functionResponse'],
|
||
|
model: ['text', 'functionCall'],
|
||
|
// System instructions shouldn't be in history anyway.
|
||
|
system: ['text']
|
||
|
};
|
||
|
const VALID_PREVIOUS_CONTENT_ROLES = {
|
||
|
user: ['model'],
|
||
|
function: ['model'],
|
||
|
model: ['user', 'function'],
|
||
|
// System instructions shouldn't be in history.
|
||
|
system: []
|
||
|
};
|
||
|
function validateChatHistory(history) {
|
||
|
let prevContent = null;
|
||
|
for (const currContent of history) {
|
||
|
const { role, parts } = currContent;
|
||
|
if (!prevContent && role !== 'user') {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, `First Content should be with role 'user', got ${role}`);
|
||
|
}
|
||
|
if (!POSSIBLE_ROLES.includes(role)) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, `Each item should include role field. Got ${role} but valid roles are: ${JSON.stringify(POSSIBLE_ROLES)}`);
|
||
|
}
|
||
|
if (!Array.isArray(parts)) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, `Content should have 'parts' but property with an array of Parts`);
|
||
|
}
|
||
|
if (parts.length === 0) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, `Each Content should have at least one part`);
|
||
|
}
|
||
|
const countFields = {
|
||
|
text: 0,
|
||
|
inlineData: 0,
|
||
|
functionCall: 0,
|
||
|
functionResponse: 0
|
||
|
};
|
||
|
for (const part of parts) {
|
||
|
for (const key of VALID_PART_FIELDS) {
|
||
|
if (key in part) {
|
||
|
countFields[key] += 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const validParts = VALID_PARTS_PER_ROLE[role];
|
||
|
for (const key of VALID_PART_FIELDS) {
|
||
|
if (!validParts.includes(key) && countFields[key] > 0) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, `Content with role '${role}' can't contain '${key}' part`);
|
||
|
}
|
||
|
}
|
||
|
if (prevContent) {
|
||
|
const validPreviousContentRoles = VALID_PREVIOUS_CONTENT_ROLES[role];
|
||
|
if (!validPreviousContentRoles.includes(prevContent.role)) {
|
||
|
throw new VertexAIError("invalid-content" /* VertexAIErrorCode.INVALID_CONTENT */, `Content with role '${role} can't follow '${prevContent.role}'. Valid previous roles: ${JSON.stringify(VALID_PREVIOUS_CONTENT_ROLES)}`);
|
||
|
}
|
||
|
}
|
||
|
prevContent = currContent;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Do not log a message for this error.
|
||
|
*/
|
||
|
const SILENT_ERROR = 'SILENT_ERROR';
|
||
|
/**
|
||
|
* ChatSession class that enables sending chat messages and stores
|
||
|
* history of sent and received messages so far.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class ChatSession {
|
||
|
constructor(apiSettings, model, params, requestOptions) {
|
||
|
this.model = model;
|
||
|
this.params = params;
|
||
|
this.requestOptions = requestOptions;
|
||
|
this._history = [];
|
||
|
this._sendPromise = Promise.resolve();
|
||
|
this._apiSettings = apiSettings;
|
||
|
if (params === null || params === void 0 ? void 0 : params.history) {
|
||
|
validateChatHistory(params.history);
|
||
|
this._history = params.history;
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Gets the chat history so far. Blocked prompts are not added to history.
|
||
|
* Neither blocked candidates nor the prompts that generated them are added
|
||
|
* to history.
|
||
|
*/
|
||
|
async getHistory() {
|
||
|
await this._sendPromise;
|
||
|
return this._history;
|
||
|
}
|
||
|
/**
|
||
|
* Sends a chat message and receives a non-streaming
|
||
|
* <code>{@link GenerateContentResult}</code>
|
||
|
*/
|
||
|
async sendMessage(request) {
|
||
|
var _a, _b, _c, _d, _e;
|
||
|
await this._sendPromise;
|
||
|
const newContent = formatNewContent(request);
|
||
|
const generateContentRequest = {
|
||
|
safetySettings: (_a = this.params) === null || _a === void 0 ? void 0 : _a.safetySettings,
|
||
|
generationConfig: (_b = this.params) === null || _b === void 0 ? void 0 : _b.generationConfig,
|
||
|
tools: (_c = this.params) === null || _c === void 0 ? void 0 : _c.tools,
|
||
|
toolConfig: (_d = this.params) === null || _d === void 0 ? void 0 : _d.toolConfig,
|
||
|
systemInstruction: (_e = this.params) === null || _e === void 0 ? void 0 : _e.systemInstruction,
|
||
|
contents: [...this._history, newContent]
|
||
|
};
|
||
|
let finalResult = {};
|
||
|
// Add onto the chain.
|
||
|
this._sendPromise = this._sendPromise
|
||
|
.then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.requestOptions))
|
||
|
.then(result => {
|
||
|
var _a, _b;
|
||
|
if (result.response.candidates &&
|
||
|
result.response.candidates.length > 0) {
|
||
|
this._history.push(newContent);
|
||
|
const responseContent = {
|
||
|
parts: ((_a = result.response.candidates) === null || _a === void 0 ? void 0 : _a[0].content.parts) || [],
|
||
|
// Response seems to come back without a role set.
|
||
|
role: ((_b = result.response.candidates) === null || _b === void 0 ? void 0 : _b[0].content.role) || 'model'
|
||
|
};
|
||
|
this._history.push(responseContent);
|
||
|
}
|
||
|
else {
|
||
|
const blockErrorMessage = formatBlockErrorMessage(result.response);
|
||
|
if (blockErrorMessage) {
|
||
|
console.warn(`sendMessage() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.`);
|
||
|
}
|
||
|
}
|
||
|
finalResult = result;
|
||
|
});
|
||
|
await this._sendPromise;
|
||
|
return finalResult;
|
||
|
}
|
||
|
/**
|
||
|
* Sends a chat message and receives the response as a
|
||
|
* <code>{@link GenerateContentStreamResult}</code> containing an iterable stream
|
||
|
* and a response promise.
|
||
|
*/
|
||
|
async sendMessageStream(request) {
|
||
|
var _a, _b, _c, _d, _e;
|
||
|
await this._sendPromise;
|
||
|
const newContent = formatNewContent(request);
|
||
|
const generateContentRequest = {
|
||
|
safetySettings: (_a = this.params) === null || _a === void 0 ? void 0 : _a.safetySettings,
|
||
|
generationConfig: (_b = this.params) === null || _b === void 0 ? void 0 : _b.generationConfig,
|
||
|
tools: (_c = this.params) === null || _c === void 0 ? void 0 : _c.tools,
|
||
|
toolConfig: (_d = this.params) === null || _d === void 0 ? void 0 : _d.toolConfig,
|
||
|
systemInstruction: (_e = this.params) === null || _e === void 0 ? void 0 : _e.systemInstruction,
|
||
|
contents: [...this._history, newContent]
|
||
|
};
|
||
|
const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.requestOptions);
|
||
|
// Add onto the chain.
|
||
|
this._sendPromise = this._sendPromise
|
||
|
.then(() => streamPromise)
|
||
|
// This must be handled to avoid unhandled rejection, but jump
|
||
|
// to the final catch block with a label to not log this error.
|
||
|
.catch(_ignored => {
|
||
|
throw new Error(SILENT_ERROR);
|
||
|
})
|
||
|
.then(streamResult => streamResult.response)
|
||
|
.then(response => {
|
||
|
if (response.candidates && response.candidates.length > 0) {
|
||
|
this._history.push(newContent);
|
||
|
const responseContent = Object.assign({}, response.candidates[0].content);
|
||
|
// Response seems to come back without a role set.
|
||
|
if (!responseContent.role) {
|
||
|
responseContent.role = 'model';
|
||
|
}
|
||
|
this._history.push(responseContent);
|
||
|
}
|
||
|
else {
|
||
|
const blockErrorMessage = formatBlockErrorMessage(response);
|
||
|
if (blockErrorMessage) {
|
||
|
console.warn(`sendMessageStream() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.`);
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
.catch(e => {
|
||
|
// Errors in streamPromise are already catchable by the user as
|
||
|
// streamPromise is returned.
|
||
|
// Avoid duplicating the error message in logs.
|
||
|
if (e.message !== SILENT_ERROR) {
|
||
|
// Users do not have access to _sendPromise to catch errors
|
||
|
// downstream from streamPromise, so they should not throw.
|
||
|
console.error(e);
|
||
|
}
|
||
|
});
|
||
|
return streamPromise;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
async function countTokens(apiSettings, model, params, requestOptions) {
|
||
|
const response = await makeRequest(model, Task.COUNT_TOKENS, apiSettings, false, JSON.stringify(params), requestOptions);
|
||
|
return response.json();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Class for generative model APIs.
|
||
|
* @public
|
||
|
*/
|
||
|
class GenerativeModel {
|
||
|
constructor(vertexAI, modelParams, requestOptions) {
|
||
|
var _a, _b, _c, _d;
|
||
|
if (!((_b = (_a = vertexAI.app) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.apiKey)) {
|
||
|
throw new VertexAIError("no-api-key" /* VertexAIErrorCode.NO_API_KEY */, `The "apiKey" field is empty in the local Firebase config. Firebase VertexAI requires this field to contain a valid API key.`);
|
||
|
}
|
||
|
else if (!((_d = (_c = vertexAI.app) === null || _c === void 0 ? void 0 : _c.options) === null || _d === void 0 ? void 0 : _d.projectId)) {
|
||
|
throw new VertexAIError("no-project-id" /* VertexAIErrorCode.NO_PROJECT_ID */, `The "projectId" field is empty in the local Firebase config. Firebase VertexAI requires this field to contain a valid project ID.`);
|
||
|
}
|
||
|
else {
|
||
|
this._apiSettings = {
|
||
|
apiKey: vertexAI.app.options.apiKey,
|
||
|
project: vertexAI.app.options.projectId,
|
||
|
location: vertexAI.location
|
||
|
};
|
||
|
if (vertexAI.appCheck) {
|
||
|
this._apiSettings.getAppCheckToken = () => vertexAI.appCheck.getToken();
|
||
|
}
|
||
|
if (vertexAI.auth) {
|
||
|
this._apiSettings.getAuthToken = () => vertexAI.auth.getToken();
|
||
|
}
|
||
|
}
|
||
|
if (modelParams.model.includes('/')) {
|
||
|
if (modelParams.model.startsWith('models/')) {
|
||
|
// Add "publishers/google" if the user is only passing in 'models/model-name'.
|
||
|
this.model = `publishers/google/${modelParams.model}`;
|
||
|
}
|
||
|
else {
|
||
|
// Any other custom format (e.g. tuned models) must be passed in correctly.
|
||
|
this.model = modelParams.model;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// If path is not included, assume it's a non-tuned model.
|
||
|
this.model = `publishers/google/models/${modelParams.model}`;
|
||
|
}
|
||
|
this.generationConfig = modelParams.generationConfig || {};
|
||
|
this.safetySettings = modelParams.safetySettings || [];
|
||
|
this.tools = modelParams.tools;
|
||
|
this.toolConfig = modelParams.toolConfig;
|
||
|
this.systemInstruction = formatSystemInstruction(modelParams.systemInstruction);
|
||
|
this.requestOptions = requestOptions || {};
|
||
|
}
|
||
|
/**
|
||
|
* Makes a single non-streaming call to the model
|
||
|
* and returns an object containing a single <code>{@link GenerateContentResponse}</code>.
|
||
|
*/
|
||
|
async generateContent(request) {
|
||
|
const formattedParams = formatGenerateContentInput(request);
|
||
|
return generateContent(this._apiSettings, this.model, Object.assign({ generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, toolConfig: this.toolConfig, systemInstruction: this.systemInstruction }, formattedParams), this.requestOptions);
|
||
|
}
|
||
|
/**
|
||
|
* Makes a single streaming call to the model
|
||
|
* and returns an object containing an iterable stream that iterates
|
||
|
* over all chunks in the streaming response as well as
|
||
|
* a promise that returns the final aggregated response.
|
||
|
*/
|
||
|
async generateContentStream(request) {
|
||
|
const formattedParams = formatGenerateContentInput(request);
|
||
|
return generateContentStream(this._apiSettings, this.model, Object.assign({ generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, toolConfig: this.toolConfig, systemInstruction: this.systemInstruction }, formattedParams), this.requestOptions);
|
||
|
}
|
||
|
/**
|
||
|
* Gets a new <code>{@link ChatSession}</code> instance which can be used for
|
||
|
* multi-turn chats.
|
||
|
*/
|
||
|
startChat(startChatParams) {
|
||
|
return new ChatSession(this._apiSettings, this.model, Object.assign({ tools: this.tools, toolConfig: this.toolConfig, systemInstruction: this.systemInstruction }, startChatParams), this.requestOptions);
|
||
|
}
|
||
|
/**
|
||
|
* Counts the tokens in the provided request.
|
||
|
*/
|
||
|
async countTokens(request) {
|
||
|
const formattedParams = formatGenerateContentInput(request);
|
||
|
return countTokens(this._apiSettings, this.model, formattedParams);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Parent class encompassing all Schema types, with static methods that
|
||
|
* allow building specific Schema types. This class can be converted with
|
||
|
* `JSON.stringify()` into a JSON string accepted by Vertex AI REST endpoints.
|
||
|
* (This string conversion is automatically done when calling SDK methods.)
|
||
|
* @public
|
||
|
*/
|
||
|
class Schema {
|
||
|
constructor(schemaParams) {
|
||
|
// eslint-disable-next-line guard-for-in
|
||
|
for (const paramKey in schemaParams) {
|
||
|
this[paramKey] = schemaParams[paramKey];
|
||
|
}
|
||
|
// Ensure these are explicitly set to avoid TS errors.
|
||
|
this.type = schemaParams.type;
|
||
|
this.nullable = schemaParams.hasOwnProperty('nullable')
|
||
|
? !!schemaParams.nullable
|
||
|
: false;
|
||
|
}
|
||
|
/**
|
||
|
* Defines how this Schema should be serialized as JSON.
|
||
|
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#tojson_behavior
|
||
|
* @internal
|
||
|
*/
|
||
|
toJSON() {
|
||
|
const obj = {
|
||
|
type: this.type
|
||
|
};
|
||
|
for (const prop in this) {
|
||
|
if (this.hasOwnProperty(prop) && this[prop] !== undefined) {
|
||
|
if (prop !== 'required' || this.type === SchemaType.OBJECT) {
|
||
|
obj[prop] = this[prop];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return obj;
|
||
|
}
|
||
|
static array(arrayParams) {
|
||
|
return new ArraySchema(arrayParams, arrayParams.items);
|
||
|
}
|
||
|
static object(objectParams) {
|
||
|
return new ObjectSchema(objectParams, objectParams.properties, objectParams.optionalProperties);
|
||
|
}
|
||
|
// eslint-disable-next-line id-blacklist
|
||
|
static string(stringParams) {
|
||
|
return new StringSchema(stringParams);
|
||
|
}
|
||
|
static enumString(stringParams) {
|
||
|
return new StringSchema(stringParams, stringParams.enum);
|
||
|
}
|
||
|
static integer(integerParams) {
|
||
|
return new IntegerSchema(integerParams);
|
||
|
}
|
||
|
// eslint-disable-next-line id-blacklist
|
||
|
static number(numberParams) {
|
||
|
return new NumberSchema(numberParams);
|
||
|
}
|
||
|
// eslint-disable-next-line id-blacklist
|
||
|
static boolean(booleanParams) {
|
||
|
return new BooleanSchema(booleanParams);
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Schema class for "integer" types.
|
||
|
* @public
|
||
|
*/
|
||
|
class IntegerSchema extends Schema {
|
||
|
constructor(schemaParams) {
|
||
|
super(Object.assign({ type: SchemaType.INTEGER }, schemaParams));
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Schema class for "number" types.
|
||
|
* @public
|
||
|
*/
|
||
|
class NumberSchema extends Schema {
|
||
|
constructor(schemaParams) {
|
||
|
super(Object.assign({ type: SchemaType.NUMBER }, schemaParams));
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Schema class for "boolean" types.
|
||
|
* @public
|
||
|
*/
|
||
|
class BooleanSchema extends Schema {
|
||
|
constructor(schemaParams) {
|
||
|
super(Object.assign({ type: SchemaType.BOOLEAN }, schemaParams));
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Schema class for "string" types. Can be used with or without
|
||
|
* enum values.
|
||
|
* @public
|
||
|
*/
|
||
|
class StringSchema extends Schema {
|
||
|
constructor(schemaParams, enumValues) {
|
||
|
super(Object.assign({ type: SchemaType.STRING }, schemaParams));
|
||
|
this.enum = enumValues;
|
||
|
}
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
toJSON() {
|
||
|
const obj = super.toJSON();
|
||
|
if (this.enum) {
|
||
|
obj['enum'] = this.enum;
|
||
|
}
|
||
|
return obj;
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Schema class for "array" types.
|
||
|
* The `items` param should refer to the type of item that can be a member
|
||
|
* of the array.
|
||
|
* @public
|
||
|
*/
|
||
|
class ArraySchema extends Schema {
|
||
|
constructor(schemaParams, items) {
|
||
|
super(Object.assign({ type: SchemaType.ARRAY }, schemaParams));
|
||
|
this.items = items;
|
||
|
}
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
toJSON() {
|
||
|
const obj = super.toJSON();
|
||
|
obj.items = this.items.toJSON();
|
||
|
return obj;
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Schema class for "object" types.
|
||
|
* The `properties` param must be a map of `Schema` objects.
|
||
|
* @public
|
||
|
*/
|
||
|
class ObjectSchema extends Schema {
|
||
|
constructor(schemaParams, properties, optionalProperties = []) {
|
||
|
super(Object.assign({ type: SchemaType.OBJECT }, schemaParams));
|
||
|
this.properties = properties;
|
||
|
this.optionalProperties = optionalProperties;
|
||
|
}
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
toJSON() {
|
||
|
const obj = super.toJSON();
|
||
|
obj.properties = Object.assign({}, this.properties);
|
||
|
const required = [];
|
||
|
if (this.optionalProperties) {
|
||
|
for (const propertyKey of this.optionalProperties) {
|
||
|
if (!this.properties.hasOwnProperty(propertyKey)) {
|
||
|
throw new VertexAIError("invalid-schema" /* VertexAIErrorCode.INVALID_SCHEMA */, `Property "${propertyKey}" specified in "optionalProperties" does not exist.`);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (const propertyKey in this.properties) {
|
||
|
if (this.properties.hasOwnProperty(propertyKey)) {
|
||
|
obj.properties[propertyKey] = this.properties[propertyKey].toJSON();
|
||
|
if (!this.optionalProperties.includes(propertyKey)) {
|
||
|
required.push(propertyKey);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (required.length > 0) {
|
||
|
obj.required = required;
|
||
|
}
|
||
|
delete obj.optionalProperties;
|
||
|
return obj;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2024 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.
|
||
|
*/
|
||
|
/**
|
||
|
* Returns a <code>{@link VertexAI}</code> instance for the given app.
|
||
|
*
|
||
|
* @public
|
||
|
*
|
||
|
* @param app - The {@link @firebase/app#FirebaseApp} to use.
|
||
|
*/
|
||
|
function getVertexAI(app = getApp(), options) {
|
||
|
app = getModularInstance(app);
|
||
|
// Dependencies
|
||
|
const vertexProvider = _getProvider(app, VERTEX_TYPE);
|
||
|
return vertexProvider.getImmediate({
|
||
|
identifier: (options === null || options === void 0 ? void 0 : options.location) || DEFAULT_LOCATION
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Returns a <code>{@link GenerativeModel}</code> class with methods for inference
|
||
|
* and other functionality.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
function getGenerativeModel(vertexAI, modelParams, requestOptions) {
|
||
|
if (!modelParams.model) {
|
||
|
throw new VertexAIError("no-model" /* VertexAIErrorCode.NO_MODEL */, `Must provide a model name. Example: getGenerativeModel({ model: 'my-model-name' })`);
|
||
|
}
|
||
|
return new GenerativeModel(vertexAI, modelParams, requestOptions);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The Vertex AI in Firebase Web SDK.
|
||
|
*
|
||
|
* @packageDocumentation
|
||
|
*/
|
||
|
function registerVertex() {
|
||
|
_registerComponent(new Component(VERTEX_TYPE, (container, { instanceIdentifier: location }) => {
|
||
|
// getImmediate for FirebaseApp will always succeed
|
||
|
const app = container.getProvider('app').getImmediate();
|
||
|
const auth = container.getProvider('auth-internal');
|
||
|
const appCheckProvider = container.getProvider('app-check-internal');
|
||
|
return new VertexAIService(app, auth, appCheckProvider, { location });
|
||
|
}, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
||
|
registerVersion(name, version);
|
||
|
// BUILD_TARGET will be replaced by values like esm2017, cjs2017, etc during the compilation
|
||
|
registerVersion(name, version, 'esm2017');
|
||
|
}
|
||
|
registerVertex();
|
||
|
|
||
|
export { ArraySchema, BlockReason, BooleanSchema, ChatSession, FinishReason, FunctionCallingMode, GenerativeModel, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, IntegerSchema, NumberSchema, ObjectSchema, POSSIBLE_ROLES, Schema, SchemaType, StringSchema, VertexAIError, getGenerativeModel, getVertexAI };
|
||
|
//# sourceMappingURL=index.esm2017.js.map
|