From 61af23655d72d841895d64637ef21375b4a612d2 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 22 Mar 2024 10:56:45 -0700 Subject: [PATCH 01/28] Initial vertex SDK setup (#281) --- packages/firebase/package.json | 18 +++- packages/firebase/vertexai/index.ts | 18 ++++ packages/firebase/vertexai/package.json | 7 ++ packages/vertexai/.eslintrc.js | 35 ++++++++ packages/vertexai/CHANGELOG.md | 2 + packages/vertexai/README.md | 4 + packages/vertexai/api-extractor.json | 10 +++ packages/vertexai/karma.conf.js | 35 ++++++++ packages/vertexai/package.json | 80 ++++++++++++++++++ packages/vertexai/rollup.config.js | 105 ++++++++++++++++++++++++ packages/vertexai/src/api.ts | 70 ++++++++++++++++ packages/vertexai/src/constants.ts | 18 ++++ packages/vertexai/src/factory.ts | 53 ++++++++++++ packages/vertexai/src/index.test.ts | 27 ++++++ packages/vertexai/src/index.ts | 58 +++++++++++++ packages/vertexai/src/public-types.ts | 33 ++++++++ packages/vertexai/tsconfig.json | 9 ++ 17 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 packages/firebase/vertexai/index.ts create mode 100644 packages/firebase/vertexai/package.json create mode 100644 packages/vertexai/.eslintrc.js create mode 100644 packages/vertexai/CHANGELOG.md create mode 100644 packages/vertexai/README.md create mode 100644 packages/vertexai/api-extractor.json create mode 100644 packages/vertexai/karma.conf.js create mode 100644 packages/vertexai/package.json create mode 100644 packages/vertexai/rollup.config.js create mode 100644 packages/vertexai/src/api.ts create mode 100644 packages/vertexai/src/constants.ts create mode 100644 packages/vertexai/src/factory.ts create mode 100644 packages/vertexai/src/index.test.ts create mode 100644 packages/vertexai/src/index.ts create mode 100644 packages/vertexai/src/public-types.ts create mode 100644 packages/vertexai/tsconfig.json diff --git a/packages/firebase/package.json b/packages/firebase/package.json index 35425f5244d..fca83a76409 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -215,6 +215,18 @@ }, "default": "./storage/dist/esm/index.esm.js" }, + "./vertexai": { + "types": "./vertexai/dist/vertexai/index.d.ts", + "node": { + "require": "./vertexai/dist/index.cjs.js", + "import": "./vertexai/dist/index.mjs" + }, + "browser": { + "require": "./vertexai/dist/index.cjs.js", + "import": "./vertexai/dist/esm/index.esm.js" + }, + "default": "./vertexai/dist/esm/index.esm.js" + }, "./compat/analytics": { "types": "./compat/analytics/dist/compat/analytics/index.d.ts", "node": { @@ -399,7 +411,8 @@ "@firebase/analytics-compat": "0.2.7", "@firebase/app-check": "0.8.2", "@firebase/app-check-compat": "0.3.9", - "@firebase/util": "1.9.4" + "@firebase/util": "1.9.4", + "@firebase/vertexai": "0.0.1" }, "devDependencies": { "rollup": "2.79.1", @@ -431,7 +444,8 @@ "remote-config", "messaging", "messaging/sw", - "database" + "database", + "vertexai" ], "typings": "empty.d.ts" } diff --git a/packages/firebase/vertexai/index.ts b/packages/firebase/vertexai/index.ts new file mode 100644 index 00000000000..2645fd3004f --- /dev/null +++ b/packages/firebase/vertexai/index.ts @@ -0,0 +1,18 @@ +/** + * @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. + */ + +export * from '@firebase/vertexai'; diff --git a/packages/firebase/vertexai/package.json b/packages/firebase/vertexai/package.json new file mode 100644 index 00000000000..20e04a3bbb5 --- /dev/null +++ b/packages/firebase/vertexai/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/vertexai", + "main": "dist/index.cjs.js", + "browser": "dist/esm/index.esm.js", + "module": "dist/esm/index.esm.js", + "typings": "dist/vertexai/index.d.ts" +} \ No newline at end of file diff --git a/packages/vertexai/.eslintrc.js b/packages/vertexai/.eslintrc.js new file mode 100644 index 00000000000..1e8712b0633 --- /dev/null +++ b/packages/vertexai/.eslintrc.js @@ -0,0 +1,35 @@ +/** + * @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 path = require('path'); + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname] + } + ] + } +}; diff --git a/packages/vertexai/CHANGELOG.md b/packages/vertexai/CHANGELOG.md new file mode 100644 index 00000000000..3cb406f1c23 --- /dev/null +++ b/packages/vertexai/CHANGELOG.md @@ -0,0 +1,2 @@ +# @firebase/vertexai + diff --git a/packages/vertexai/README.md b/packages/vertexai/README.md new file mode 100644 index 00000000000..a6227543588 --- /dev/null +++ b/packages/vertexai/README.md @@ -0,0 +1,4 @@ +# @firebase/vertexai + + + diff --git a/packages/vertexai/api-extractor.json b/packages/vertexai/api-extractor.json new file mode 100644 index 00000000000..8a3c6cb251e --- /dev/null +++ b/packages/vertexai/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/api-extractor.json", + // Point it to your entry point d.ts file. + "mainEntryPointFilePath": "/dist/src/index.d.ts", + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" + } +} \ No newline at end of file diff --git a/packages/vertexai/karma.conf.js b/packages/vertexai/karma.conf.js new file mode 100644 index 00000000000..3fe2a2f9633 --- /dev/null +++ b/packages/vertexai/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @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 karmaBase = require('../../config/karma.base'); + +const files = [`src/**/*.test.ts`]; + +module.exports = function (config) { + const karmaConfig = { + ...karmaBase, + // files to load into karma + files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }; + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json new file mode 100644 index 00000000000..bddb228ae6d --- /dev/null +++ b/packages/vertexai/package.json @@ -0,0 +1,80 @@ +{ + "name": "@firebase/vertexai", + "version": "0.0.1", + "private": true, + "description": "A template package for new firebase packages", + "author": "Firebase (https://firebase.google.com/)", + "engines": { + "node": ">=18.0.0" + }, + "main": "dist/index.cjs.js", + "browser": "dist/esm/index.esm2017.js", + "module": "dist/esm/index.esm2017.js", + "esm5": "dist/index.esm5.js", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "node": { + "require": "./dist/index.cjs.js", + "import": "./dist/esm/index.esm2017.js" + }, + "esm5": "./dist/index.esm5.js", + "browser": { + "require": "./dist/index.cjs.js", + "import": "./dist/esm/index.esm2017.js" + }, + "default": "./dist/esm/index.esm2017.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:deps": "lerna run --scope @firebase/template --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p --npm-path npm lint test:all", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:all": "run-p --npm-path npm test:browser test:node", + "test:browser": "karma start --single-run", + "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.* --config ../../config/mocharc.node.js" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + }, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.0", + "@firebase/component": "0.6.5", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.4", + "tslib": "^2.1.0" + }, + "license": "Apache-2.0", + "devDependencies": { + "@firebase/app": "0.9.28", + "@rollup/plugin-json": "4.1.0", + "rollup": "2.79.1", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-typescript2": "0.31.2", + "typescript": "4.7.4" + }, + "repository": { + "directory": "packages/vertexai", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + } +} \ No newline at end of file diff --git a/packages/vertexai/rollup.config.js b/packages/vertexai/rollup.config.js new file mode 100644 index 00000000000..add0a061191 --- /dev/null +++ b/packages/vertexai/rollup.config.js @@ -0,0 +1,105 @@ +/** + * @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. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import replace from 'rollup-plugin-replace'; +import typescript from 'typescript'; +import pkg from './package.json'; +import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target'; +import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file'; + +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); + +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json() +]; + +const browserBuilds = [ + { + input: 'src/index.ts', + output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], + plugins: [ + ...es5BuildPlugins, + replace(generateBuildTargetReplaceConfig('esm', 5)) + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'src/index.ts', + output: { + file: pkg.module, + format: 'es', + sourcemap: true + }, + plugins: [ + ...es2017BuildPlugins, + replace(generateBuildTargetReplaceConfig('esm', 2017)), + emitModulePackageFile() + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'src/index.ts', + output: { + file: './dist/index.cjs.js', + format: 'cjs', + sourcemap: true + }, + plugins: [ + ...es2017BuildPlugins, + replace(generateBuildTargetReplaceConfig('cjs', 2017)) + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +// const nodeBuilds = [ +// { +// input: 'index.node.ts', +// output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], +// plugins: es2017BuildPlugins, +// external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) +// }, +// { +// input: 'index.node.ts', +// output: [ +// { file: pkg.exports['.'].node.import, format: 'es', sourcemap: true } +// ], +// plugins: [...es2017BuildPlugins, emitModulePackageFile()], +// external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) +// } +// ]; + +export default [...browserBuilds]; diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts new file mode 100644 index 00000000000..fc281fc5279 --- /dev/null +++ b/packages/vertexai/src/api.ts @@ -0,0 +1,70 @@ +/** + * @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. + */ + +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; +import { Provider } from '@firebase/component'; +import { getModularInstance } from '@firebase/util'; +import { VERTEX_TYPE } from './constants'; +import { VertexService } from './factory'; +import { Vertex } from './public-types'; + +declare module '@firebase/component' { + interface NameServiceMapping { + [VERTEX_TYPE]: VertexService; + } +} + +/** + * Returns an {@link Vertex} instance for the given app. + * + * @public + * + * @param app - The {@link @firebase/app#FirebaseApp} to use. + */ +export function getVertex(app: FirebaseApp = getApp()): Vertex { + app = getModularInstance(app); + // Dependencies + const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); + + if (vertexProvider.isInitialized()) { + return vertexProvider.getImmediate(); + } + + return vertexProvider.initialize(); +} + +export function getGenerativeModel(vertex: Vertex): GenerativeModel { + return new GenerativeModel(vertex, {}); +} + +// Just a stub +class GenerativeModel { + private _apiKey?: string; + constructor(vertex: Vertex, modelParams: {}) { + if (!vertex.app.options.apiKey) { + // throw error + } else { + this._apiKey = vertex.app.options.apiKey; + //TODO: remove when we use this + console.log(this._apiKey); + } + //TODO: do something with modelParams + console.log(modelParams); + } +} + +//TODO: add all top-level exportable methods and classes diff --git a/packages/vertexai/src/constants.ts b/packages/vertexai/src/constants.ts new file mode 100644 index 00000000000..b30d1105c12 --- /dev/null +++ b/packages/vertexai/src/constants.ts @@ -0,0 +1,18 @@ +/** + * @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. + */ + +export const VERTEX_TYPE = 'vertex'; diff --git a/packages/vertexai/src/factory.ts b/packages/vertexai/src/factory.ts new file mode 100644 index 00000000000..3c7724f3757 --- /dev/null +++ b/packages/vertexai/src/factory.ts @@ -0,0 +1,53 @@ +/** + * @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. + */ + +import { FirebaseApp, _FirebaseService } from '@firebase/app'; +import { Vertex, VertexOptions } from './public-types'; +import { + AppCheckInternalComponentName, + FirebaseAppCheckInternal +} from '@firebase/app-check-interop-types'; +import { Provider } from '@firebase/component'; + +export function factory( + app: FirebaseApp, + appCheckProvider?: Provider, + options?: VertexOptions +): VertexService { + return new VertexService(app, appCheckProvider, options); +} + +export class VertexService implements Vertex, _FirebaseService { + appCheck: FirebaseAppCheckInternal | null; + + constructor( + public app: FirebaseApp, + appCheckProvider?: Provider, + public options?: VertexOptions + ) { + const appCheck = appCheckProvider?.getImmediate({ optional: true }); + this.appCheck = appCheck || null; + // This needs to go into the url + if (this.options?.region) { + console.log(this.options?.region); + } + } + + _delete(): Promise { + return Promise.resolve(); + } +} diff --git a/packages/vertexai/src/index.test.ts b/packages/vertexai/src/index.test.ts new file mode 100644 index 00000000000..95a9329f2d0 --- /dev/null +++ b/packages/vertexai/src/index.test.ts @@ -0,0 +1,27 @@ +/** + * @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. + */ + +import { expect } from 'chai'; + +describe('Simple test', () => { + it('Should skip this test'); + it('Should test this async thing', async () => { + // Do some async assertions, you can use `await` syntax if it helps + const val = await Promise.resolve(42); + expect(val).to.equal(42); + }); +}); diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts new file mode 100644 index 00000000000..682d1298710 --- /dev/null +++ b/packages/vertexai/src/index.ts @@ -0,0 +1,58 @@ +/** + * The Firebase Vertex Web SDK. + * + * @packageDocumentation + */ + +/** + * @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. + */ + +import { registerVersion, _registerComponent } from '@firebase/app'; +import { factory } from './factory'; +import { VERTEX_TYPE } from './constants'; +import { Component, ComponentType } from '@firebase/component'; +import { name, version } from '../package.json'; + +declare global { + interface Window { + [key: string]: unknown; + } +} + +function registerVertex(): void { + _registerComponent( + new Component( + VERTEX_TYPE, + (container, { instanceIdentifier: region }) => { + // getImmediate for FirebaseApp will always succeed + const app = container.getProvider('app').getImmediate(); + const appCheckProvider = container.getProvider('app-check-internal'); + return factory(app, appCheckProvider, { region }); + }, + ComponentType.PUBLIC + ).setMultipleInstances(true) + ); + + registerVersion(name, version); + // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation + registerVersion(name, version, '__BUILD_TARGET__'); +} + +registerVertex(); + +export * from './api'; +export * from './public-types'; diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts new file mode 100644 index 00000000000..16050e8c6cf --- /dev/null +++ b/packages/vertexai/src/public-types.ts @@ -0,0 +1,33 @@ +/** + * @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. + */ + +import { FirebaseApp } from '@firebase/app'; + +/** + * An instance of Firebase Vertex. + * @public + */ +export interface Vertex { + /** + * The {@link @firebase/app#FirebaseApp} this {@link Vertex} instance is associated with. + */ + app: FirebaseApp; +} + +export interface VertexOptions { + region?: string; +} diff --git a/packages/vertexai/tsconfig.json b/packages/vertexai/tsconfig.json new file mode 100644 index 00000000000..a06ed9a374c --- /dev/null +++ b/packages/vertexai/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file From 57018cae9904147e4a501cef73eb32a194052eb2 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 26 Mar 2024 13:28:45 -0700 Subject: [PATCH 02/28] Port code from GoogleAI repo (#283) --- .gitignore | 3 + packages/vertexai/README.md | 3 +- packages/vertexai/package.json | 5 +- packages/vertexai/src/api.test.ts | 65 +++ packages/vertexai/src/api.ts | 34 +- packages/vertexai/src/constants.ts | 2 + packages/vertexai/src/errors.ts | 63 +++ packages/vertexai/src/factory.ts | 7 +- .../src/methods/chat-session-helpers.test.ts | 157 +++++++ .../src/methods/chat-session-helpers.ts | 108 +++++ .../vertexai/src/methods/chat-session.test.ts | 97 +++++ packages/vertexai/src/methods/chat-session.ts | 186 ++++++++ packages/vertexai/src/methods/count-tokens.ts | 39 ++ .../vertexai/src/methods/embed-content.ts | 67 +++ .../src/methods/generate-content.test.ts | 194 +++++++++ .../vertexai/src/methods/generate-content.ts | 74 ++++ .../src/models/generative-model.test.ts | 56 +++ .../vertexai/src/models/generative-model.ts | 190 +++++++++ packages/vertexai/src/public-types.ts | 3 + .../vertexai/src/requests/request-helpers.ts | 108 +++++ .../vertexai/src/requests/request.test.ts | 179 ++++++++ packages/vertexai/src/requests/request.ts | 126 ++++++ .../src/requests/response-helpers.test.ts | 154 +++++++ .../vertexai/src/requests/response-helpers.ts | 143 +++++++ .../src/requests/stream-reader.test.ts | 400 ++++++++++++++++++ .../vertexai/src/requests/stream-reader.ts | 195 +++++++++ packages/vertexai/src/types/content.ts | 118 ++++++ packages/vertexai/src/types/enums.ts | 119 ++++++ packages/vertexai/src/types/index.ts | 21 + packages/vertexai/src/types/internal.ts | 22 + packages/vertexai/src/types/requests.ts | 238 +++++++++++ packages/vertexai/src/types/responses.ts | 157 +++++++ packages/vertexai/test-utils/base64cat.ts | 19 + packages/vertexai/test-utils/cat.jpeg | Bin 0 -> 35160 bytes packages/vertexai/test-utils/cat.png | Bin 0 -> 142387 bytes packages/vertexai/test-utils/convert-mocks.ts | 53 +++ packages/vertexai/test-utils/mock-response.ts | 65 +++ .../streaming-failure-empty-content.txt | 2 + ...streaming-failure-finish-reason-safety.txt | 2 + ...treaming-failure-prompt-blocked-safety.txt | 2 + ...treaming-failure-recitation-no-content.txt | 6 + .../streaming-success-basic-reply-long.txt | 12 + .../streaming-success-basic-reply-short.txt | 2 + .../streaming-success-citations.txt | 12 + .../streaming-success-function-call-short.txt | 2 + .../mock-responses/streaming-success-utf8.txt | 8 + .../mock-responses/streaming-unknown-enum.txt | 12 + .../unary-failure-empty-content.json | 28 ++ .../unary-failure-finish-reason-safety.json | 53 +++ .../unary-failure-image-rejected.json | 13 + .../unary-failure-prompt-blocked-safety.json | 23 + .../unary-success-basic-reply-long.json | 52 +++ .../unary-success-basic-reply-short.json | 52 +++ .../unary-success-citations.json | 64 +++ .../mock-responses/unary-unknown-enum.json | 52 +++ packages/vertexai/tsconfig.json | 2 +- 56 files changed, 3842 insertions(+), 27 deletions(-) create mode 100644 packages/vertexai/src/api.test.ts create mode 100644 packages/vertexai/src/errors.ts create mode 100644 packages/vertexai/src/methods/chat-session-helpers.test.ts create mode 100644 packages/vertexai/src/methods/chat-session-helpers.ts create mode 100644 packages/vertexai/src/methods/chat-session.test.ts create mode 100644 packages/vertexai/src/methods/chat-session.ts create mode 100644 packages/vertexai/src/methods/count-tokens.ts create mode 100644 packages/vertexai/src/methods/embed-content.ts create mode 100644 packages/vertexai/src/methods/generate-content.test.ts create mode 100644 packages/vertexai/src/methods/generate-content.ts create mode 100644 packages/vertexai/src/models/generative-model.test.ts create mode 100644 packages/vertexai/src/models/generative-model.ts create mode 100644 packages/vertexai/src/requests/request-helpers.ts create mode 100644 packages/vertexai/src/requests/request.test.ts create mode 100644 packages/vertexai/src/requests/request.ts create mode 100644 packages/vertexai/src/requests/response-helpers.test.ts create mode 100644 packages/vertexai/src/requests/response-helpers.ts create mode 100644 packages/vertexai/src/requests/stream-reader.test.ts create mode 100644 packages/vertexai/src/requests/stream-reader.ts create mode 100644 packages/vertexai/src/types/content.ts create mode 100644 packages/vertexai/src/types/enums.ts create mode 100644 packages/vertexai/src/types/index.ts create mode 100644 packages/vertexai/src/types/internal.ts create mode 100644 packages/vertexai/src/types/requests.ts create mode 100644 packages/vertexai/src/types/responses.ts create mode 100644 packages/vertexai/test-utils/base64cat.ts create mode 100644 packages/vertexai/test-utils/cat.jpeg create mode 100644 packages/vertexai/test-utils/cat.png create mode 100644 packages/vertexai/test-utils/convert-mocks.ts create mode 100644 packages/vertexai/test-utils/mock-response.ts create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-success-citations.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json diff --git a/.gitignore b/.gitignore index c2e2136eece..e15c88b7ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,6 @@ toc/ .terraform.lock.hcl *.tfstate *.tfstate.* + +# generated test case text data +mocks-lookup.ts \ No newline at end of file diff --git a/packages/vertexai/README.md b/packages/vertexai/README.md index a6227543588..b559a1e739e 100644 --- a/packages/vertexai/README.md +++ b/packages/vertexai/README.md @@ -1,4 +1,5 @@ # @firebase/vertexai +This is the Firebase Vertex AI component of the Firebase JS SDK. - +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index bddb228ae6d..01190a54e85 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -34,8 +34,9 @@ "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c", - "build:deps": "lerna run --scope @firebase/template --include-dependencies build", + "build:deps": "lerna run --scope @firebase/vertexai --include-dependencies build", "dev": "rollup -c -w", + "pretest": "yarn ts-node ./test-utils/convert-mocks.ts", "test": "run-p --npm-path npm lint test:all", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", "test:all": "run-p --npm-path npm test:browser test:node", @@ -55,7 +56,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app": "0.9.28", + "@firebase/app": "0.9.29", "@rollup/plugin-json": "4.1.0", "rollup": "2.79.1", "rollup-plugin-replace": "2.2.0", diff --git a/packages/vertexai/src/api.test.ts b/packages/vertexai/src/api.test.ts new file mode 100644 index 00000000000..b8a657c8a8f --- /dev/null +++ b/packages/vertexai/src/api.test.ts @@ -0,0 +1,65 @@ +/** + * @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. + */ +import { ModelParams } from './types'; +import { getGenerativeModel } from './api'; +import { expect } from 'chai'; +import { Vertex } from './public-types'; +import { GenerativeModel } from './models/generative-model'; +import { VertexError } from './errors'; + +const fakeVertex: Vertex = { + app: { + name: 'DEFAULT', + automaticDataCollectionEnabled: true, + options: { + apiKey: 'key', + projectId: 'my-project' + } + }, + region: 'us-central1' +}; + +describe('Top level API', () => { + it('getGenerativeModel throws if no model is provided', () => { + expect(() => getGenerativeModel(fakeVertex, {} as ModelParams)).to.throw( + VertexError.NO_MODEL + ); + }); + it('getGenerativeModel throws if no apiKey is provided', () => { + const fakeVertexNoApiKey = { + ...fakeVertex, + app: { options: { projectId: 'my-project' } } + } as Vertex; + expect(() => + getGenerativeModel(fakeVertexNoApiKey, { model: 'my-model' }) + ).to.throw(VertexError.NO_API_KEY); + }); + it('getGenerativeModel throws if no projectId is provided', () => { + const fakeVertexNoProject = { + ...fakeVertex, + app: { options: { apiKey: 'my-key' } } + } as Vertex; + expect(() => + getGenerativeModel(fakeVertexNoProject, { model: 'my-model' }) + ).to.throw(VertexError.NO_PROJECT_ID); + }); + it('getGenerativeModel gets a GenerativeModel', () => { + const genModel = getGenerativeModel(fakeVertex, { model: 'my-model' }); + expect(genModel).to.be.an.instanceOf(GenerativeModel); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); +}); diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index fc281fc5279..f9593dfd01d 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -21,6 +21,13 @@ import { getModularInstance } from '@firebase/util'; import { VERTEX_TYPE } from './constants'; import { VertexService } from './factory'; import { Vertex } from './public-types'; +import { ERROR_FACTORY, VertexError } from './errors'; +import { ModelParams, RequestOptions } from './types'; +import { GenerativeModel } from './models/generative-model'; + +export { ChatSession } from './methods/chat-session'; + +export { GenerativeModel }; declare module '@firebase/component' { interface NameServiceMapping { @@ -47,24 +54,13 @@ export function getVertex(app: FirebaseApp = getApp()): Vertex { return vertexProvider.initialize(); } -export function getGenerativeModel(vertex: Vertex): GenerativeModel { - return new GenerativeModel(vertex, {}); -} - -// Just a stub -class GenerativeModel { - private _apiKey?: string; - constructor(vertex: Vertex, modelParams: {}) { - if (!vertex.app.options.apiKey) { - // throw error - } else { - this._apiKey = vertex.app.options.apiKey; - //TODO: remove when we use this - console.log(this._apiKey); - } - //TODO: do something with modelParams - console.log(modelParams); +export function getGenerativeModel( + vertex: Vertex, + modelParams: ModelParams, + requestOptions?: RequestOptions +): GenerativeModel { + if (!modelParams.model) { + throw ERROR_FACTORY.create(VertexError.NO_MODEL); } + return new GenerativeModel(vertex, modelParams, requestOptions); } - -//TODO: add all top-level exportable methods and classes diff --git a/packages/vertexai/src/constants.ts b/packages/vertexai/src/constants.ts index b30d1105c12..f947f28e412 100644 --- a/packages/vertexai/src/constants.ts +++ b/packages/vertexai/src/constants.ts @@ -16,3 +16,5 @@ */ export const VERTEX_TYPE = 'vertex'; + +export const DEFAULT_REGION = 'us-central1'; diff --git a/packages/vertexai/src/errors.ts b/packages/vertexai/src/errors.ts new file mode 100644 index 00000000000..2aa72ac44ea --- /dev/null +++ b/packages/vertexai/src/errors.ts @@ -0,0 +1,63 @@ +/** + * @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. + */ + +import { ErrorFactory, ErrorMap } from '@firebase/util'; +import { GenerateContentResponse } from './types'; + +export const enum VertexError { + FETCH_ERROR = 'fetch-error', + INVALID_CONTENT = 'invalid-content', + NO_API_KEY = 'no-api-key', + NO_MODEL = 'no-model', + NO_PROJECT_ID = 'no-project-id', + PARSE_FAILED = 'parse-failed', + RESPONSE_ERROR = 'response-error' +} + +const ERRORS: ErrorMap = { + [VertexError.FETCH_ERROR]: `Error fetching from {$url}: {$message}`, + [VertexError.INVALID_CONTENT]: `Content formatting error: {$message}`, + [VertexError.NO_API_KEY]: + `The "apiKey" field is empty in the local Firebase config. Firebase VertexAI requires this field to` + + `contain a valid API key.`, + [VertexError.NO_PROJECT_ID]: + `The "projectId" field is empty in the local Firebase config. Firebase VertexAI requires this field to` + + `contain a valid project ID.`, + [VertexError.NO_MODEL]: + `Must provide a model name. ` + + `Example: genai.getGenerativeModel({ model: 'my-model-name' })`, + [VertexError.PARSE_FAILED]: `Parsing failed: {$message}`, + [VertexError.RESPONSE_ERROR]: + `Response error: {$message}. Response body stored in ` + + `error.customData.response` +}; + +interface ErrorParams { + [VertexError.FETCH_ERROR]: { url: string; message: string }; + [VertexError.INVALID_CONTENT]: { message: string }; + [VertexError.PARSE_FAILED]: { message: string }; + [VertexError.RESPONSE_ERROR]: { + message: string; + response: GenerateContentResponse; + }; +} + +export const ERROR_FACTORY = new ErrorFactory( + 'vertex', + 'Vertex', + ERRORS +); diff --git a/packages/vertexai/src/factory.ts b/packages/vertexai/src/factory.ts index 3c7724f3757..f071497aad0 100644 --- a/packages/vertexai/src/factory.ts +++ b/packages/vertexai/src/factory.ts @@ -22,6 +22,7 @@ import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; import { Provider } from '@firebase/component'; +import { DEFAULT_REGION } from './constants'; export function factory( app: FirebaseApp, @@ -33,6 +34,7 @@ export function factory( export class VertexService implements Vertex, _FirebaseService { appCheck: FirebaseAppCheckInternal | null; + region: string; constructor( public app: FirebaseApp, @@ -41,10 +43,7 @@ export class VertexService implements Vertex, _FirebaseService { ) { const appCheck = appCheckProvider?.getImmediate({ optional: true }); this.appCheck = appCheck || null; - // This needs to go into the url - if (this.options?.region) { - console.log(this.options?.region); - } + this.region = this.options?.region || DEFAULT_REGION; } _delete(): Promise { diff --git a/packages/vertexai/src/methods/chat-session-helpers.test.ts b/packages/vertexai/src/methods/chat-session-helpers.test.ts new file mode 100644 index 00000000000..feab9fc3b05 --- /dev/null +++ b/packages/vertexai/src/methods/chat-session-helpers.test.ts @@ -0,0 +1,157 @@ +/** + * @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. + */ + +import { validateChatHistory } from './chat-session-helpers'; +import { expect } from 'chai'; +import { Content } from '../types'; +import { FirebaseError } from '@firebase/util'; + +describe('chat-session-helpers', () => { + describe('validateChatHistory', () => { + const TCS: Array<{ history: Content[]; isValid: boolean }> = [ + { + history: [{ role: 'user', parts: [{ text: 'hi' }] }], + isValid: true + }, + { + history: [ + { + role: 'user', + parts: [ + { text: 'hi' }, + { inlineData: { mimeType: 'image/jpeg', data: 'base64==' } } + ] + } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { role: 'model', parts: [{ text: 'hi' }, { text: 'hi' }] } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [{ functionCall: { name: 'greet', args: { name: 'user' } } }] + } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [{ functionCall: { name: 'greet', args: { name: 'user' } } }] + }, + { + role: 'function', + parts: [ + { + functionResponse: { name: 'greet', response: { name: 'user' } } + } + ] + } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [{ functionCall: { name: 'greet', args: { name: 'user' } } }] + }, + { + role: 'function', + parts: [ + { + functionResponse: { name: 'greet', response: { name: 'user' } } + } + ] + }, + { + role: 'model', + parts: [{ text: 'hi name' }] + } + ], + isValid: true + }, + { + //@ts-expect-error + history: [{ role: 'user', parts: '' }], + isValid: false + }, + { + //@ts-expect-error + history: [{ role: 'user' }], + isValid: false + }, + { + history: [{ role: 'user', parts: [] }], + isValid: false + }, + { + history: [{ role: 'model', parts: [{ text: 'hi' }] }], + isValid: false + }, + { + history: [ + { + role: 'function', + parts: [ + { + functionResponse: { name: 'greet', response: { name: 'user' } } + } + ] + } + ], + isValid: false + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { role: 'user', parts: [{ text: 'hi' }] } + ], + isValid: false + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { role: 'model', parts: [{ text: 'hi' }] }, + { role: 'model', parts: [{ text: 'hi' }] } + ], + isValid: false + } + ]; + TCS.forEach((tc, index) => { + it(`case ${index}`, () => { + const fn = (): void => validateChatHistory(tc.history); + if (tc.isValid) { + expect(fn).to.not.throw(); + } else { + expect(fn).to.throw(FirebaseError); + } + }); + }); + }); +}); diff --git a/packages/vertexai/src/methods/chat-session-helpers.ts b/packages/vertexai/src/methods/chat-session-helpers.ts new file mode 100644 index 00000000000..b797c3071ff --- /dev/null +++ b/packages/vertexai/src/methods/chat-session-helpers.ts @@ -0,0 +1,108 @@ +/** + * @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. + */ + +import { Content, POSSIBLE_ROLES, Part, Role } from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; + +// https://ai.google.dev/api/rest/v1beta/Content#part + +const VALID_PART_FIELDS: Array = [ + 'text', + 'inlineData', + 'functionCall', + 'functionResponse' +]; + +const VALID_PARTS_PER_ROLE: { [key in Role]: Array } = { + user: ['text', 'inlineData'], + function: ['functionResponse'], + model: ['text', 'functionCall'] +}; + +const VALID_PREVIOUS_CONTENT_ROLES: { [key in Role]: Role[] } = { + user: ['model'], + function: ['model'], + model: ['user', 'function'] +}; + +export function validateChatHistory(history: Content[]): void { + let prevContent: Content | null = null; + for (const currContent of history) { + const { role, parts } = currContent; + if (!prevContent && role !== 'user') { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `First content should be with role 'user', got ${role}` + }); + } + if (!POSSIBLE_ROLES.includes(role)) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `Each item should include role field. Got ${role} but valid roles are: ${JSON.stringify( + POSSIBLE_ROLES + )}` + }); + } + + if (!Array.isArray(parts)) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: "Content should have 'parts' property with an array of Parts" + }); + } + + if (parts.length === 0) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: 'Each Content should have at least one part' + }); + } + + const countFields: Record = { + 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 ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `Content with role '${role}' can't contain '${key}' part` + }); + } + } + + if (prevContent) { + const validPreviousContentRoles = VALID_PREVIOUS_CONTENT_ROLES[role]; + if (!validPreviousContentRoles.includes(prevContent.role)) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `Content with role '${role}' can't follow '${ + prevContent.role + }'. Valid previous roles: ${JSON.stringify( + VALID_PREVIOUS_CONTENT_ROLES + )}` + }); + } + } + prevContent = currContent; + } +} diff --git a/packages/vertexai/src/methods/chat-session.test.ts b/packages/vertexai/src/methods/chat-session.test.ts new file mode 100644 index 00000000000..c8c92e046b9 --- /dev/null +++ b/packages/vertexai/src/methods/chat-session.test.ts @@ -0,0 +1,97 @@ +/** + * @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. + */ + +import { expect, use } from 'chai'; +import { match, restore, stub, useFakeTimers } from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as generateContentMethods from './generate-content'; +import { GenerateContentStreamResult } from '../types'; +import { ChatSession } from './chat-session'; +import { ApiSettings } from '../types/internal'; + +use(sinonChai); +use(chaiAsPromised); + +const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'my-project', + location: 'us-central1' +}; + +describe('ChatSession', () => { + afterEach(() => { + restore(); + }); + describe('sendMessage()', () => { + it('generateContent errors should be catchable', async () => { + const generateContentStub = stub( + generateContentMethods, + 'generateContent' + ).rejects('generateContent failed'); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + await expect(chatSession.sendMessage('hello')).to.be.rejected; + expect(generateContentStub).to.be.calledWith( + fakeApiSettings, + 'a-model', + match.any + ); + }); + }); + describe('sendMessageStream()', () => { + it('generateContentStream errors should be catchable', async () => { + const clock = useFakeTimers(); + const consoleStub = stub(console, 'error'); + const generateContentStreamStub = stub( + generateContentMethods, + 'generateContentStream' + ).rejects('generateContentStream failed'); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + await expect(chatSession.sendMessageStream('hello')).to.be.rejected; + expect(generateContentStreamStub).to.be.calledWith( + fakeApiSettings, + 'a-model', + match.any + ); + await clock.runAllAsync(); + expect(consoleStub).to.not.be.called; + clock.restore(); + }); + it('downstream sendPromise errors should log but not throw', async () => { + const clock = useFakeTimers(); + const consoleStub = stub(console, 'error'); + // make response undefined so that response.candidates errors + const generateContentStreamStub = stub( + generateContentMethods, + 'generateContentStream' + ).resolves({} as unknown as GenerateContentStreamResult); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + await chatSession.sendMessageStream('hello'); + expect(generateContentStreamStub).to.be.calledWith( + fakeApiSettings, + 'a-model', + match.any + ); + await clock.runAllAsync(); + expect(consoleStub.args[0][0].toString()).to.include( + // Firefox has different wording when a property is undefined + 'undefined' + ); + clock.restore(); + }); + }); +}); diff --git a/packages/vertexai/src/methods/chat-session.ts b/packages/vertexai/src/methods/chat-session.ts new file mode 100644 index 00000000000..2f49b1131e7 --- /dev/null +++ b/packages/vertexai/src/methods/chat-session.ts @@ -0,0 +1,186 @@ +/** + * @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. + */ + +import { + Content, + GenerateContentRequest, + GenerateContentResult, + GenerateContentStreamResult, + Part, + RequestOptions, + StartChatParams +} from '../types'; +import { formatNewContent } from '../requests/request-helpers'; +import { formatBlockErrorMessage } from '../requests/response-helpers'; +import { validateChatHistory } from './chat-session-helpers'; +import { generateContent, generateContentStream } from './generate-content'; +import { ApiSettings } from '../types/internal'; + +/** + * 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 + */ +export class ChatSession { + private _apiSettings: ApiSettings; + private _history: Content[] = []; + private _sendPromise: Promise = Promise.resolve(); + + constructor( + apiSettings: ApiSettings, + public model: string, + public params?: StartChatParams, + public requestOptions?: RequestOptions + ) { + this._apiSettings = apiSettings; + if (params?.history) { + validateChatHistory(params.history); + this._history = params.history; + } + } + + /** + * Gets the chat history so far. Blocked prompts are not added to history. + * Blocked candidates are not added to history, nor are the prompts that + * generated them. + */ + async getHistory(): Promise { + await this._sendPromise; + return this._history; + } + + /** + * Sends a chat message and receives a non-streaming + * {@link GenerateContentResult} + */ + async sendMessage( + request: string | Array + ): Promise { + await this._sendPromise; + const newContent = formatNewContent(request); + const generateContentRequest: GenerateContentRequest = { + safetySettings: this.params?.safetySettings, + generationConfig: this.params?.generationConfig, + tools: this.params?.tools, + contents: [...this._history, newContent] + }; + let finalResult = {} as GenerateContentResult; + // Add onto the chain. + this._sendPromise = this._sendPromise + .then(() => + generateContent( + this._apiSettings, + this.model, + generateContentRequest, + this.requestOptions + ) + ) + .then(result => { + if ( + result.response.candidates && + result.response.candidates.length > 0 + ) { + this._history.push(newContent); + const responseContent: Content = { + parts: result.response.candidates?.[0].content.parts || [], + // Response seems to come back without a role set. + role: result.response.candidates?.[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 + * {@link GenerateContentStreamResult} containing an iterable stream + * and a response promise. + */ + async sendMessageStream( + request: string | Array + ): Promise { + await this._sendPromise; + const newContent = formatNewContent(request); + const generateContentRequest: GenerateContentRequest = { + safetySettings: this.params?.safetySettings, + generationConfig: this.params?.generationConfig, + tools: this.params?.tools, + 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 = { ...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; + } +} diff --git a/packages/vertexai/src/methods/count-tokens.ts b/packages/vertexai/src/methods/count-tokens.ts new file mode 100644 index 00000000000..5473e9f4db0 --- /dev/null +++ b/packages/vertexai/src/methods/count-tokens.ts @@ -0,0 +1,39 @@ +/** + * @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. + */ + +import { + CountTokensRequest, + CountTokensResponse, + RequestOptions +} from '../types'; +import { RequestUrl, Task, makeRequest } from '../requests/request'; +import { ApiSettings } from '../types/internal'; + +export async function countTokens( + apiSettings: ApiSettings, + model: string, + params: CountTokensRequest, + requestOptions?: RequestOptions +): Promise { + const url = new RequestUrl(model, Task.COUNT_TOKENS, apiSettings, false, {}); + const response = await makeRequest( + url, + JSON.stringify({ ...params, model }), + requestOptions + ); + return response.json(); +} diff --git a/packages/vertexai/src/methods/embed-content.ts b/packages/vertexai/src/methods/embed-content.ts new file mode 100644 index 00000000000..e1f1b2fcb6d --- /dev/null +++ b/packages/vertexai/src/methods/embed-content.ts @@ -0,0 +1,67 @@ +/** + * @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. + */ + +import { + BatchEmbedContentsRequest, + BatchEmbedContentsResponse, + EmbedContentRequest, + EmbedContentResponse, + RequestOptions +} from '../types'; +import { RequestUrl, Task, makeRequest } from '../requests/request'; +import { ApiSettings } from '../types/internal'; + +export async function embedContent( + apiSettings: ApiSettings, + model: string, + params: EmbedContentRequest, + requestOptions?: RequestOptions +): Promise { + const url = new RequestUrl(model, Task.EMBED_CONTENT, apiSettings, false, {}); + const response = await makeRequest( + url, + JSON.stringify(params), + requestOptions + ); + return response.json(); +} + +export async function batchEmbedContents( + apiSettings: ApiSettings, + model: string, + params: BatchEmbedContentsRequest, + requestOptions?: RequestOptions +): Promise { + const url = new RequestUrl( + model, + Task.BATCH_EMBED_CONTENTS, + apiSettings, + false, + {} + ); + const requestsWithModel: EmbedContentRequest[] = params.requests.map( + request => { + return { ...request, model }; + } + ); + const response = await makeRequest( + url, + JSON.stringify({ requests: requestsWithModel }), + requestOptions + ); + return response.json(); +} diff --git a/packages/vertexai/src/methods/generate-content.test.ts b/packages/vertexai/src/methods/generate-content.test.ts new file mode 100644 index 00000000000..ff2666d2924 --- /dev/null +++ b/packages/vertexai/src/methods/generate-content.test.ts @@ -0,0 +1,194 @@ +/** + * @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. + */ + +import { expect, use } from 'chai'; +import { match, restore, stub } from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import { getMockResponse } from '../../test-utils/mock-response'; +import * as request from '../requests/request'; +import { generateContent } from './generate-content'; +import { + GenerateContentRequest, + HarmBlockThreshold, + HarmCategory +} from '../types'; +import { ApiSettings } from '../types/internal'; + +use(sinonChai); +use(chaiAsPromised); + +const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'my-project', + location: 'us-central1' +}; + +const fakeRequestParams: GenerateContentRequest = { + contents: [{ parts: [{ text: 'hello' }], role: 'user' }], + generationConfig: { + topK: 16 + }, + safetySettings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE + } + ] +}; + +describe('generateContent()', () => { + afterEach(() => { + restore(); + }); + it('short response', async () => { + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('Helena'); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match((value: string) => { + return value.includes('contents'); + }) + ); + }); + it('long response', async () => { + const mockResponse = getMockResponse('unary-success-basic-reply-long.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('Use Freshly Ground Coffee'); + expect(result.response.text()).to.include('30 minutes of brewing'); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match.any + ); + }); + it('citations', async () => { + const mockResponse = getMockResponse('unary-success-citations.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('Quantum mechanics is'); + expect( + result.response.candidates?.[0].citationMetadata?.citationSources.length + ).to.equal(1); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match.any + ); + }); + it('blocked prompt', async () => { + const mockResponse = getMockResponse( + 'unary-failure-prompt-blocked-safety.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text).to.throw('SAFETY'); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match.any + ); + }); + it('finishReason safety', async () => { + const mockResponse = getMockResponse( + 'unary-failure-finish-reason-safety.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text).to.throw('SAFETY'); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match.any + ); + }); + it('empty content', async () => { + const mockResponse = getMockResponse('unary-failure-empty-content.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.equal(''); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match.any + ); + }); + it('unknown enum - should ignore', async () => { + const mockResponse = getMockResponse('unary-unknown-enum.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('30 minutes of brewing'); + expect(makeRequestStub).to.be.calledWith( + match.instanceOf(request.RequestUrl), + match.any + ); + }); + it('image rejected (400)', async () => { + const mockResponse = getMockResponse('unary-failure-image-rejected.json'); + const mockFetch = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 400, + json: mockResponse.json + } as Response); + await expect( + generateContent(fakeApiSettings, 'model', fakeRequestParams) + ).to.be.rejectedWith(/400.*invalid argument/); + expect(mockFetch).to.be.called; + }); +}); diff --git a/packages/vertexai/src/methods/generate-content.ts b/packages/vertexai/src/methods/generate-content.ts new file mode 100644 index 00000000000..92037a1f092 --- /dev/null +++ b/packages/vertexai/src/methods/generate-content.ts @@ -0,0 +1,74 @@ +/** + * @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. + */ + +import { + GenerateContentRequest, + GenerateContentResponse, + GenerateContentResult, + GenerateContentStreamResult, + RequestOptions +} from '../types'; +import { RequestUrl, Task, makeRequest } from '../requests/request'; +import { addHelpers } from '../requests/response-helpers'; +import { processStream } from '../requests/stream-reader'; +import { ApiSettings } from '../types/internal'; + +export async function generateContentStream( + apiSettings: ApiSettings, + model: string, + params: GenerateContentRequest, + requestOptions?: RequestOptions +): Promise { + const url = new RequestUrl( + model, + Task.STREAM_GENERATE_CONTENT, + apiSettings, + /* stream */ true, + requestOptions + ); + const response = await makeRequest( + url, + JSON.stringify(params), + requestOptions + ); + return processStream(response); +} + +export async function generateContent( + apiSettings: ApiSettings, + model: string, + params: GenerateContentRequest, + requestOptions?: RequestOptions +): Promise { + const url = new RequestUrl( + model, + Task.GENERATE_CONTENT, + apiSettings, + /* stream */ false, + requestOptions + ); + const response = await makeRequest( + url, + JSON.stringify(params), + requestOptions + ); + const responseJson: GenerateContentResponse = await response.json(); + const enhancedResponse = addHelpers(responseJson); + return { + response: enhancedResponse + }; +} diff --git a/packages/vertexai/src/models/generative-model.test.ts b/packages/vertexai/src/models/generative-model.test.ts new file mode 100644 index 00000000000..b9e749ba2e2 --- /dev/null +++ b/packages/vertexai/src/models/generative-model.test.ts @@ -0,0 +1,56 @@ +/** + * @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. + */ +import { expect } from 'chai'; +import { GenerativeModel } from './generative-model'; +import { Vertex } from '../public-types'; + +const fakeVertex: Vertex = { + app: { + name: 'DEFAULT', + automaticDataCollectionEnabled: true, + options: { + apiKey: 'key', + projectId: 'my-project' + } + }, + region: 'us-central1' +}; + +describe('GenerativeModel', () => { + it('handles plain model name', () => { + const genModel = new GenerativeModel(fakeVertex, { model: 'my-model' }); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); + it('handles models/ prefixed model name', () => { + const genModel = new GenerativeModel(fakeVertex, { + model: 'models/my-model' + }); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); + it('handles full model name', () => { + const genModel = new GenerativeModel(fakeVertex, { + model: 'publishers/google/models/my-model' + }); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); + it('handles prefixed tuned model name', () => { + const genModel = new GenerativeModel(fakeVertex, { + model: 'tunedModels/my-model' + }); + expect(genModel.model).to.equal('tunedModels/my-model'); + }); +}); diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts new file mode 100644 index 00000000000..62d67f26e1b --- /dev/null +++ b/packages/vertexai/src/models/generative-model.ts @@ -0,0 +1,190 @@ +/** + * @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. + */ + +import { + generateContent, + generateContentStream +} from '../methods/generate-content'; +import { + BatchEmbedContentsRequest, + BatchEmbedContentsResponse, + CountTokensRequest, + CountTokensResponse, + EmbedContentRequest, + EmbedContentResponse, + GenerateContentRequest, + GenerateContentResult, + GenerateContentStreamResult, + GenerationConfig, + ModelParams, + Part, + RequestOptions, + SafetySetting, + StartChatParams, + Tool +} from '../types'; +import { ChatSession } from '../methods/chat-session'; +import { countTokens } from '../methods/count-tokens'; +import { batchEmbedContents, embedContent } from '../methods/embed-content'; +import { + formatEmbedContentInput, + formatGenerateContentInput +} from '../requests/request-helpers'; +import { Vertex } from '../public-types'; +import { ERROR_FACTORY, VertexError } from '../errors'; +import { ApiSettings } from '../types/internal'; + +/** + * Class for generative model APIs. + * @public + */ +export class GenerativeModel { + private _apiSettings: ApiSettings; + model: string; + generationConfig: GenerationConfig; + safetySettings: SafetySetting[]; + requestOptions?: RequestOptions; + tools?: Tool[]; + + constructor( + vertex: Vertex, + modelParams: ModelParams, + requestOptions?: RequestOptions + ) { + if (!vertex.app?.options?.apiKey) { + throw ERROR_FACTORY.create(VertexError.NO_API_KEY); + } else if (!vertex.app?.options?.projectId) { + throw ERROR_FACTORY.create(VertexError.NO_PROJECT_ID); + } else { + this._apiSettings = { + apiKey: vertex.app.options.apiKey, + project: vertex.app.options.projectId, + location: vertex.region + }; + } + 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.requestOptions = requestOptions || {}; + } + + /** + * Makes a single non-streaming call to the model + * and returns an object containing a single {@link GenerateContentResponse}. + */ + async generateContent( + request: GenerateContentRequest | string | Array + ): Promise { + const formattedParams = formatGenerateContentInput(request); + return generateContent( + this._apiSettings, + this.model, + { + generationConfig: this.generationConfig, + safetySettings: this.safetySettings, + tools: this.tools, + ...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: GenerateContentRequest | string | Array + ): Promise { + const formattedParams = formatGenerateContentInput(request); + return generateContentStream( + this._apiSettings, + this.model, + { + generationConfig: this.generationConfig, + safetySettings: this.safetySettings, + tools: this.tools, + ...formattedParams + }, + this.requestOptions + ); + } + + /** + * Gets a new {@link ChatSession} instance which can be used for + * multi-turn chats. + */ + startChat(startChatParams?: StartChatParams): ChatSession { + return new ChatSession( + this._apiSettings, + this.model, + { + tools: this.tools, + ...startChatParams + }, + this.requestOptions + ); + } + + /** + * Counts the tokens in the provided request. + */ + async countTokens( + request: CountTokensRequest | string | Array + ): Promise { + const formattedParams = formatGenerateContentInput(request); + return countTokens(this._apiSettings, this.model, formattedParams); + } + + /** + * Embeds the provided content. + */ + async embedContent( + request: EmbedContentRequest | string | Array + ): Promise { + const formattedParams = formatEmbedContentInput(request); + return embedContent(this._apiSettings, this.model, formattedParams); + } + + /** + * Embeds an array of {@link EmbedContentRequest}s. + */ + async batchEmbedContents( + batchEmbedContentRequest: BatchEmbedContentsRequest + ): Promise { + return batchEmbedContents( + this._apiSettings, + this.model, + batchEmbedContentRequest, + this.requestOptions + ); + } +} diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index 16050e8c6cf..63df8c527fa 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -17,6 +17,8 @@ import { FirebaseApp } from '@firebase/app'; +export * from './types'; + /** * An instance of Firebase Vertex. * @public @@ -26,6 +28,7 @@ export interface Vertex { * The {@link @firebase/app#FirebaseApp} this {@link Vertex} instance is associated with. */ app: FirebaseApp; + region: string; } export interface VertexOptions { diff --git a/packages/vertexai/src/requests/request-helpers.ts b/packages/vertexai/src/requests/request-helpers.ts new file mode 100644 index 00000000000..7745e4b0c65 --- /dev/null +++ b/packages/vertexai/src/requests/request-helpers.ts @@ -0,0 +1,108 @@ +/** + * @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. + */ + +import { + Content, + EmbedContentRequest, + GenerateContentRequest, + Part +} from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; + +export function formatNewContent( + request: string | Array +): Content { + let newParts: Part[] = []; + 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: Part[] +): Content { + const userContent: Content = { role: 'user', parts: [] }; + const functionContent: Content = { 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 ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: + 'Within a single message, FunctionResponse cannot be mixed with other type of part in the request for sending chat message.' + }); + } + + if (!hasUserContent && !hasFunctionContent) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: 'No content is provided for sending chat message.' + }); + } + + if (hasUserContent) { + return userContent; + } + + return functionContent; +} + +export function formatGenerateContentInput( + params: GenerateContentRequest | string | Array +): GenerateContentRequest { + if ((params as GenerateContentRequest).contents) { + return params as GenerateContentRequest; + } else { + const content = formatNewContent(params as string | Array); + return { contents: [content] }; + } +} + +export function formatEmbedContentInput( + params: EmbedContentRequest | string | Array +): EmbedContentRequest { + if (typeof params === 'string' || Array.isArray(params)) { + const content = formatNewContent(params); + return { content }; + } + return params; +} diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts new file mode 100644 index 00000000000..f7fa33e74b1 --- /dev/null +++ b/packages/vertexai/src/requests/request.test.ts @@ -0,0 +1,179 @@ +/** + * @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. + */ + +import { expect, use } from 'chai'; +import { restore, stub } from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import { DEFAULT_API_VERSION, RequestUrl, Task, makeRequest } from './request'; +import { ApiSettings } from '../types/internal'; + +use(sinonChai); +use(chaiAsPromised); + +const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'my-project', + location: 'us-central1' +}; + +const fakeRequestUrl = new RequestUrl( + 'model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + true, + {} +); + +describe('request methods', () => { + afterEach(() => { + restore(); + }); + describe('RequestUrl', () => { + it('stream', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + true, + {} + ); + expect(url.toString()).to.include('models/model-name:generateContent'); + expect(url.toString()).to.not.include(fakeApiSettings); + expect(url.toString()).to.include('alt=sse'); + }); + it('non-stream', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + {} + ); + expect(url.toString()).to.include('models/model-name:generateContent'); + expect(url.toString()).to.not.include(fakeApiSettings); + expect(url.toString()).to.not.include('alt=sse'); + }); + it('default apiVersion', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + {} + ); + expect(url.toString()).to.include(DEFAULT_API_VERSION); + }); + it('custom apiVersion', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + { apiVersion: 'v100omega' } + ); + expect(url.toString()).to.include( + '/v100omega/projects/my-project/locations/us-central1/models/model-name' + ); + }); + it('non-stream - tunedModels/', async () => { + const url = new RequestUrl( + 'tunedModels/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + {} + ); + expect(url.toString()).to.include( + 'tunedModels/model-name:generateContent' + ); + expect(url.toString()).to.not.include(fakeApiSettings); + expect(url.toString()).to.not.include('alt=sse'); + }); + }); + describe('makeRequest', () => { + it('no error', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: true + } as Response); + const response = await makeRequest(fakeRequestUrl, ''); + expect(fetchStub).to.be.calledOnce; + expect(response.ok).to.be.true; + }); + it('error with timeout', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'AbortError' + } as Response); + + await expect( + makeRequest(fakeRequestUrl, '', { + timeout: 0 + }) + ).to.be.rejectedWith('500 AbortError'); + expect(fetchStub).to.be.calledOnce; + }); + it('Network error, no response.json()', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'Server Error' + } as Response); + await expect(makeRequest(fakeRequestUrl, '')).to.be.rejectedWith( + /500 Server Error/ + ); + expect(fetchStub).to.be.calledOnce; + }); + it('Network error, includes response.json()', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'Server Error', + json: () => Promise.resolve({ error: { message: 'extra info' } }) + } as Response); + await expect(makeRequest(fakeRequestUrl, '')).to.be.rejectedWith( + /500 Server Error.*extra info/ + ); + expect(fetchStub).to.be.calledOnce; + }); + it('Network error, includes response.json() and details', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'Server Error', + json: () => + Promise.resolve({ + error: { + message: 'extra info', + details: [ + { + '@type': 'type.googleapis.com/google.rpc.DebugInfo', + detail: + '[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short' + } + ] + } + }) + } as Response); + await expect(makeRequest(fakeRequestUrl, '')).to.be.rejectedWith( + /500 Server Error.*extra info.*generic::invalid_argument/ + ); + expect(fetchStub).to.be.calledOnce; + }); + }); +}); diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts new file mode 100644 index 00000000000..37e5c183696 --- /dev/null +++ b/packages/vertexai/src/requests/request.ts @@ -0,0 +1,126 @@ +/** + * @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. + */ + +import { RequestOptions } from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; +import { ApiSettings } from '../types/internal'; + +const BASE_URL = 'https://staging-firebaseml.sandbox.googleapis.com'; + +export const DEFAULT_API_VERSION = 'v2beta'; + +/** + * We can't `require` package.json if this runs on web. We will use rollup to + * swap in the version number here at build time. + */ +const PACKAGE_VERSION = '__PACKAGE_VERSION__'; +const PACKAGE_LOG_HEADER = 'firebase-vertexai-js'; + +export enum Task { + GENERATE_CONTENT = 'generateContent', + STREAM_GENERATE_CONTENT = 'streamGenerateContent', + COUNT_TOKENS = 'countTokens', + EMBED_CONTENT = 'embedContent', + BATCH_EMBED_CONTENTS = 'batchEmbedContents' +} + +export class RequestUrl { + constructor( + public model: string, + public task: Task, + public apiSettings: ApiSettings, + public stream: boolean, + public requestOptions?: RequestOptions + ) {} + toString(): string { + const apiVersion = this.requestOptions?.apiVersion || DEFAULT_API_VERSION; + let url = `${BASE_URL}/${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; + } +} + +/** + * Simple, but may become more complex if we add more versions to log. + */ +function getClientHeaders(): string { + return `${PACKAGE_LOG_HEADER}/${PACKAGE_VERSION}`; +} + +export async function makeRequest( + url: RequestUrl, + body: string, + requestOptions?: RequestOptions +): Promise { + let response; + try { + response = await fetch(url.toString(), { + ...buildFetchOptions(requestOptions), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-goog-api-client': getClientHeaders(), + 'x-goog-api-key': url.apiSettings.apiKey + }, + body + }); + if (!response.ok) { + let message = ''; + try { + const json = await response.json(); + message = json.error.message; + if (json.error.details) { + message += ` ${JSON.stringify(json.error.details)}`; + } + } catch (e) { + // ignored + } + throw new Error(`[${response.status} ${response.statusText}] ${message}`); + } + } catch (caughtError) { + const e = caughtError as Error; + const err = ERROR_FACTORY.create(VertexError.FETCH_ERROR, { + url: url.toString(), + message: 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?: RequestOptions): RequestInit { + const fetchOptions = {} as RequestInit; + if (requestOptions?.timeout && requestOptions?.timeout >= 0) { + const abortController = new AbortController(); + const signal = abortController.signal; + setTimeout(() => abortController.abort(), requestOptions.timeout); + fetchOptions.signal = signal; + } + return fetchOptions; +} diff --git a/packages/vertexai/src/requests/response-helpers.test.ts b/packages/vertexai/src/requests/response-helpers.test.ts new file mode 100644 index 00000000000..1244fb10d67 --- /dev/null +++ b/packages/vertexai/src/requests/response-helpers.test.ts @@ -0,0 +1,154 @@ +/** + * @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. + */ + +import { addHelpers, formatBlockErrorMessage } from './response-helpers'; +import { expect, use } from 'chai'; +import { restore } from 'sinon'; +import sinonChai from 'sinon-chai'; +import { + BlockReason, + Content, + FinishReason, + GenerateContentResponse +} from '../types'; + +use(sinonChai); + +const fakeResponseText: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [{ text: 'Some text' }, { text: ' and some more text' }] + } + } + ] +}; +const fakeResponseFunctionCall: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [ + { + functionCall: { + name: 'find_theaters', + args: { + location: 'Mountain View, CA', + movie: 'Barbie' + } + } + } + ] + } + } + ] +}; + +const badFakeResponse: GenerateContentResponse = { + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [] + } +}; + +describe('response-helpers methods', () => { + afterEach(() => { + restore(); + }); + describe('addHelpers', () => { + it('good response text', async () => { + const enhancedResponse = addHelpers(fakeResponseText); + expect(enhancedResponse.text()).to.equal('Some text and some more text'); + }); + it('good response functionCall', async () => { + const enhancedResponse = addHelpers(fakeResponseFunctionCall); + expect(enhancedResponse.functionCall()).to.deep.equal( + fakeResponseFunctionCall.candidates?.[0].content.parts[0].functionCall + ); + }); + it('bad response safety', async () => { + const enhancedResponse = addHelpers(badFakeResponse); + expect(enhancedResponse.text).to.throw('SAFETY'); + }); + }); + describe('getBlockString', () => { + it('has no promptFeedback or bad finishReason', async () => { + const message = formatBlockErrorMessage({ + candidates: [ + { + index: 0, + finishReason: FinishReason.STOP, + finishMessage: 'this was fine', + content: {} as Content + } + ] + }); + expect(message).to.equal(''); + }); + it('has promptFeedback and blockReason only', async () => { + const message = formatBlockErrorMessage({ + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [] + } + }); + expect(message).to.include('Response was blocked due to SAFETY'); + }); + it('has promptFeedback with blockReason and blockMessage', async () => { + const message = formatBlockErrorMessage({ + promptFeedback: { + blockReason: BlockReason.SAFETY, + blockReasonMessage: 'safety reasons', + safetyRatings: [] + } + }); + expect(message).to.include( + 'Response was blocked due to SAFETY: safety reasons' + ); + }); + it('has bad finishReason only', async () => { + const message = formatBlockErrorMessage({ + candidates: [ + { + index: 0, + finishReason: FinishReason.SAFETY, + content: {} as Content + } + ] + }); + expect(message).to.include('Candidate was blocked due to SAFETY'); + }); + it('has finishReason and finishMessage', async () => { + const message = formatBlockErrorMessage({ + candidates: [ + { + index: 0, + finishReason: FinishReason.SAFETY, + finishMessage: 'unsafe candidate', + content: {} as Content + } + ] + }); + expect(message).to.include( + 'Candidate was blocked due to SAFETY: unsafe candidate' + ); + }); + }); +}); diff --git a/packages/vertexai/src/requests/response-helpers.ts b/packages/vertexai/src/requests/response-helpers.ts new file mode 100644 index 00000000000..6de39fb8639 --- /dev/null +++ b/packages/vertexai/src/requests/response-helpers.ts @@ -0,0 +1,143 @@ +/** + * @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. + */ + +import { + EnhancedGenerateContentResponse, + FinishReason, + FunctionCall, + GenerateContentCandidate, + GenerateContentResponse +} from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; + +/** + * Adds convenience helper methods to a response object, including stream + * chunks (as long as each chunk is a complete GenerateContentResponse JSON). + */ +export function addHelpers( + response: GenerateContentResponse +): EnhancedGenerateContentResponse { + (response as EnhancedGenerateContentResponse).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 ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `${formatBlockErrorMessage(response)}`, + response + }); + } + return getText(response); + } else if (response.promptFeedback) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `Text not available. ${formatBlockErrorMessage(response)}`, + response + }); + } + return ''; + }; + (response as EnhancedGenerateContentResponse).functionCall = () => { + if (response.candidates && response.candidates.length > 0) { + if (response.candidates.length > 1) { + console.warn( + `This response had ${response.candidates.length} ` + + `candidates. Returning function call from the first candidate only. ` + + `Access response.candidates directly to use the other candidates.` + ); + } + if (hadBadFinishReason(response.candidates[0])) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `${formatBlockErrorMessage(response)}`, + response + }); + } + return getFunctionCall(response); + } else if (response.promptFeedback) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `Function call not available. ${formatBlockErrorMessage( + response + )}`, + response + }); + } + return undefined; + }; + return response as EnhancedGenerateContentResponse; +} + +/** + * Returns text of first candidate. + */ +export function getText(response: GenerateContentResponse): string { + if (response.candidates?.[0].content?.parts?.[0]?.text) { + return response.candidates[0].content.parts + .map(({ text }) => text) + .join(''); + } else { + return ''; + } +} + +/** + * Returns {@link FunctionCall} associated with first candidate. + */ +export function getFunctionCall( + response: GenerateContentResponse +): FunctionCall | undefined { + return response.candidates?.[0].content?.parts?.[0]?.functionCall; +} + +const badFinishReasons = [FinishReason.RECITATION, FinishReason.SAFETY]; + +function hadBadFinishReason(candidate: GenerateContentCandidate): boolean { + return ( + !!candidate.finishReason && + badFinishReasons.includes(candidate.finishReason) + ); +} + +export function formatBlockErrorMessage( + response: GenerateContentResponse +): string { + let message = ''; + if ( + (!response.candidates || response.candidates.length === 0) && + response.promptFeedback + ) { + message += 'Response was blocked'; + if (response.promptFeedback?.blockReason) { + message += ` due to ${response.promptFeedback.blockReason}`; + } + if (response.promptFeedback?.blockReasonMessage) { + message += `: ${response.promptFeedback.blockReasonMessage}`; + } + } else if (response.candidates?.[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; +} diff --git a/packages/vertexai/src/requests/stream-reader.test.ts b/packages/vertexai/src/requests/stream-reader.test.ts new file mode 100644 index 00000000000..34fef7a0265 --- /dev/null +++ b/packages/vertexai/src/requests/stream-reader.test.ts @@ -0,0 +1,400 @@ +/** + * @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. + */ + +import { + aggregateResponses, + getResponseStream, + processStream +} from './stream-reader'; +import { expect, use } from 'chai'; +import { restore } from 'sinon'; +import sinonChai from 'sinon-chai'; +import { + getChunkedStream, + getMockResponseStreaming +} from '../../test-utils/mock-response'; +import { + BlockReason, + FinishReason, + GenerateContentResponse, + HarmCategory, + HarmProbability +} from '../types'; + +use(sinonChai); + +describe('getResponseStream', () => { + afterEach(() => { + restore(); + }); + it('two lines', async () => { + const src = [{ text: 'A' }, { text: 'B' }]; + const inputStream = getChunkedStream( + src + .map(v => JSON.stringify(v)) + .map(v => 'data: ' + v + '\r\n\r\n') + .join('') + ).pipeThrough(new TextDecoderStream('utf8', { fatal: true })); + const responseStream = getResponseStream<{ text: string }>(inputStream); + const reader = responseStream.getReader(); + const responses: Array<{ text: string }> = []; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + responses.push(value); + } + expect(responses).to.deep.equal(src); + }); +}); + +describe('processStream', () => { + afterEach(() => { + restore(); + }); + it('streaming response - short', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-basic-reply-short.txt' + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('Cheyenne'); + }); + it('streaming response - long', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-basic-reply-long.txt' + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('**Cats:**'); + expect(aggregatedResponse.text()).to.include('to their owners.'); + }); + it('streaming response - long - big chunk', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-basic-reply-long.txt', + 1e6 + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('**Cats:**'); + expect(aggregatedResponse.text()).to.include('to their owners.'); + }); + it('streaming response - utf8', async () => { + const fakeResponse = getMockResponseStreaming('streaming-success-utf8.txt'); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('秋风瑟瑟,叶落纷纷'); + expect(aggregatedResponse.text()).to.include('家人围坐在一起'); + }); + it('streaming response - functioncall', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-function-call-short.txt' + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.be.empty; + expect(response.functionCall()).to.be.deep.equal({ + name: 'getTemperature', + args: { city: 'San Jose' } + }); + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.be.empty; + expect(aggregatedResponse.functionCall()).to.be.deep.equal({ + name: 'getTemperature', + args: { city: 'San Jose' } + }); + }); + it('candidate had finishReason', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-finish-reason-safety.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.candidates?.[0].finishReason).to.equal('SAFETY'); + expect(aggregatedResponse.text).to.throw('SAFETY'); + for await (const response of result.stream) { + expect(response.text).to.throw('SAFETY'); + } + }); + it('prompt was blocked', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-prompt-blocked-safety.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text).to.throw('SAFETY'); + expect(aggregatedResponse.promptFeedback?.blockReason).to.equal('SAFETY'); + for await (const response of result.stream) { + expect(response.text).to.throw('SAFETY'); + } + }); + it('empty content', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-empty-content.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.equal(''); + for await (const response of result.stream) { + expect(response.text()).to.equal(''); + } + }); + it('unknown enum - should ignore', async () => { + const fakeResponse = getMockResponseStreaming('streaming-unknown-enum.txt'); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('Cats'); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + }); + it('recitation ending with a missing content field', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-recitation-no-content.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text).to.throw('RECITATION'); + expect(aggregatedResponse.candidates?.[0].content.parts[0].text).to.include( + 'Copyrighted text goes here' + ); + for await (const response of result.stream) { + if (response.candidates?.[0].finishReason !== FinishReason.RECITATION) { + expect(response.text()).to.not.be.empty; + } else { + expect(response.text).to.throw('RECITATION'); + } + } + }); + it('handles citations', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-citations.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('Quantum mechanics is'); + expect( + aggregatedResponse.candidates?.[0].citationMetadata?.citationSources + .length + ).to.equal(2); + let foundCitationMetadata = false; + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + if (response.candidates?.[0].citationMetadata) { + foundCitationMetadata = true; + } + } + expect(foundCitationMetadata).to.be.true; + }); +}); + +describe('aggregateResponses', () => { + it('handles no candidates, and promptFeedback', () => { + const responsesToAggregate: GenerateContentResponse[] = [ + { + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + probability: HarmProbability.LOW + } + ] + } + } + ]; + const response = aggregateResponses(responsesToAggregate); + expect(response.candidates).to.not.exist; + expect(response.promptFeedback?.blockReason).to.equal(BlockReason.SAFETY); + }); + describe('multiple responses, has candidates', () => { + let response: GenerateContentResponse; + before(() => { + const responsesToAggregate: GenerateContentResponse[] = [ + { + candidates: [ + { + index: 0, + content: { + role: 'user', + parts: [{ text: 'hello.' }] + }, + finishReason: FinishReason.STOP, + finishMessage: 'something', + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + probability: HarmProbability.NEGLIGIBLE + } + ] + } + ], + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + probability: HarmProbability.LOW + } + ] + } + }, + { + candidates: [ + { + index: 0, + content: { + role: 'user', + parts: [{ text: 'angry stuff' }] + }, + finishReason: FinishReason.STOP, + finishMessage: 'something', + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + probability: HarmProbability.NEGLIGIBLE + } + ], + citationMetadata: { + citationSources: [ + { + startIndex: 0, + endIndex: 20, + uri: 'sourceurl', + license: '' + } + ] + } + } + ], + promptFeedback: { + blockReason: BlockReason.OTHER, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + probability: HarmProbability.HIGH + } + ] + } + }, + { + candidates: [ + { + index: 0, + content: { + role: 'user', + parts: [{ text: '...more stuff' }] + }, + finishReason: FinishReason.MAX_TOKENS, + finishMessage: 'too many tokens', + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + probability: HarmProbability.MEDIUM + } + ], + citationMetadata: { + citationSources: [ + { + startIndex: 0, + endIndex: 20, + uri: 'sourceurl', + license: '' + }, + { + startIndex: 150, + endIndex: 155, + uri: 'sourceurl', + license: '' + } + ] + } + } + ], + promptFeedback: { + blockReason: BlockReason.OTHER, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + probability: HarmProbability.HIGH + } + ] + } + } + ]; + response = aggregateResponses(responsesToAggregate); + }); + + it('aggregates text across responses', () => { + expect(response.candidates?.length).to.equal(1); + expect( + response.candidates?.[0].content.parts.map(({ text }) => text) + ).to.deep.equal(['hello.', 'angry stuff', '...more stuff']); + }); + + it("takes the last response's promptFeedback", () => { + expect(response.promptFeedback?.blockReason).to.equal(BlockReason.OTHER); + }); + + it("takes the last response's finishReason", () => { + expect(response.candidates?.[0].finishReason).to.equal( + FinishReason.MAX_TOKENS + ); + }); + + it("takes the last response's finishMessage", () => { + expect(response.candidates?.[0].finishMessage).to.equal( + 'too many tokens' + ); + }); + + it("takes the last response's candidate safetyRatings", () => { + expect(response.candidates?.[0].safetyRatings?.[0].category).to.equal( + HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT + ); + expect(response.candidates?.[0].safetyRatings?.[0].probability).to.equal( + HarmProbability.MEDIUM + ); + }); + + it('collects all citationSources into one array', () => { + expect( + response.candidates?.[0].citationMetadata?.citationSources.length + ).to.equal(2); + expect( + response.candidates?.[0].citationMetadata?.citationSources[0].startIndex + ).to.equal(0); + expect( + response.candidates?.[0].citationMetadata?.citationSources[1].startIndex + ).to.equal(150); + }); + }); +}); diff --git a/packages/vertexai/src/requests/stream-reader.ts b/packages/vertexai/src/requests/stream-reader.ts new file mode 100644 index 00000000000..0c070cfe0f2 --- /dev/null +++ b/packages/vertexai/src/requests/stream-reader.ts @@ -0,0 +1,195 @@ +/** + * @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. + */ + +import { + EnhancedGenerateContentResponse, + GenerateContentCandidate, + GenerateContentResponse, + GenerateContentStreamResult, + Part +} from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; +import { addHelpers } from './response-helpers'; + +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 + */ +export function processStream(response: Response): GenerateContentStreamResult { + 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: ReadableStream +): Promise { + const allResponses: GenerateContentResponse[] = []; + const reader = stream.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) { + return addHelpers(aggregateResponses(allResponses)); + } + allResponses.push(value); + } +} + +async function* generateResponseSequence( + stream: ReadableStream +): AsyncGenerator { + const reader = stream.getReader(); + while (true) { + const { value, done } = await reader.read(); + if (done) { + break; + } + yield addHelpers(value); + } +} + +/** + * 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. + */ +export function getResponseStream( + inputStream: ReadableStream +): ReadableStream { + const reader = inputStream.getReader(); + const stream = new ReadableStream({ + start(controller) { + let currentText = ''; + return pump(); + function pump(): Promise<(() => Promise) | undefined> { + return reader.read().then(({ value, done }) => { + if (done) { + if (currentText.trim()) { + controller.error( + ERROR_FACTORY.create(VertexError.PARSE_FAILED, { + message: 'Failed to parse stream' + }) + ); + return; + } + controller.close(); + return; + } + + currentText += value; + let match = currentText.match(responseLineRE); + let parsedResponse: T; + while (match) { + try { + parsedResponse = JSON.parse(match[1]); + } catch (e) { + controller.error( + ERROR_FACTORY.create(VertexError.PARSE_FAILED, { + message: `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. + */ +export function aggregateResponses( + responses: GenerateContentResponse[] +): GenerateContentResponse { + const lastResponse = responses[responses.length - 1]; + const aggregatedResponse: GenerateContentResponse = { + promptFeedback: lastResponse?.promptFeedback + }; + for (const response of responses) { + if (response.candidates) { + for (const candidate of response.candidates) { + const i = candidate.index; + if (!aggregatedResponse.candidates) { + aggregatedResponse.candidates = []; + } + if (!aggregatedResponse.candidates[i]) { + aggregatedResponse.candidates[i] = { + index: candidate.index + } as GenerateContentCandidate; + } + // 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: Partial = {}; + 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 as Part + ); + } + } + } + } + } + return aggregatedResponse; +} diff --git a/packages/vertexai/src/types/content.ts b/packages/vertexai/src/types/content.ts new file mode 100644 index 00000000000..1be3b276ea7 --- /dev/null +++ b/packages/vertexai/src/types/content.ts @@ -0,0 +1,118 @@ +/** + * @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. + */ + +import { Role } from './enums'; + +/** + * Content type for both prompts and response candidates. + * @public + */ +export interface Content { + role: Role; + parts: Part[]; +} + +/** + * Content part - includes text, image/video, or function call/response part types. + * @public + */ +export type Part = + | TextPart + | InlineDataPart + | FunctionCallPart + | FunctionResponsePart; + +/** + * Content part interface if the part represents a text string. + * @public + */ +export interface TextPart { + text: string; + inlineData?: never; + functionCall?: never; + functionResponse?: never; +} + +/** + * Content part interface if the part represents an image. + * @public + */ +export interface InlineDataPart { + text?: never; + inlineData: GenerativeContentBlob; + functionCall?: never; + functionResponse?: never; +} + +/** + * Content part interface if the part represents a {@link FunctionCall}`. + * @public + */ +export interface FunctionCallPart { + text?: never; + inlineData?: never; + functionCall: FunctionCall; + functionResponse?: never; +} + +/** + * Content part interface if the part represents {@link FunctionResponse}. + * @public + */ +export interface FunctionResponsePart { + text?: never; + inlineData?: never; + functionCall?: never; + functionResponse: FunctionResponse; +} + +/** + * A predicted [FunctionCall] returned from the model + * that contains a string representing the [FunctionDeclaration.name] + * and a structured JSON object containing the parameters and their values. + * @public + */ +export interface FunctionCall { + name: string; + args: object; +} + +/** + * The result output from a [FunctionCall] that contains a string + * representing the [FunctionDeclaration.name] + * and a structured JSON object containing any output + * from the function is used as context to the model. + * This should contain the result of a [FunctionCall] + * made based on model prediction. + * @public + */ +export interface FunctionResponse { + name: string; + response: object; +} + +/** + * Interface for sending an image. + * @public + */ +export interface GenerativeContentBlob { + mimeType: string; + /** + * Image as a base64 string. + */ + data: string; +} diff --git a/packages/vertexai/src/types/enums.ts b/packages/vertexai/src/types/enums.ts new file mode 100644 index 00000000000..b08d929bede --- /dev/null +++ b/packages/vertexai/src/types/enums.ts @@ -0,0 +1,119 @@ +/** + * @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. + */ + +/** + * Role is the producer of the content. + * @public + */ +export type Role = (typeof POSSIBLE_ROLES)[number]; + +/** + * Possible roles. + * @public + */ +export const POSSIBLE_ROLES = ['user', 'model', 'function'] as const; + +/** + * Harm categories that would cause prompts or candidates to be blocked. + * @public + */ +export enum HarmCategory { + HARM_CATEGORY_UNSPECIFIED = 'HARM_CATEGORY_UNSPECIFIED', + HARM_CATEGORY_HATE_SPEECH = 'HARM_CATEGORY_HATE_SPEECH', + HARM_CATEGORY_SEXUALLY_EXPLICIT = 'HARM_CATEGORY_SEXUALLY_EXPLICIT', + HARM_CATEGORY_HARASSMENT = 'HARM_CATEGORY_HARASSMENT', + HARM_CATEGORY_DANGEROUS_CONTENT = 'HARM_CATEGORY_DANGEROUS_CONTENT' +} + +/** + * Threshold above which a prompt or candidate will be blocked. + * @public + */ +export enum HarmBlockThreshold { + // Threshold is unspecified. + HARM_BLOCK_THRESHOLD_UNSPECIFIED = 'HARM_BLOCK_THRESHOLD_UNSPECIFIED', + // Content with NEGLIGIBLE will be allowed. + BLOCK_LOW_AND_ABOVE = 'BLOCK_LOW_AND_ABOVE', + // Content with NEGLIGIBLE and LOW will be allowed. + BLOCK_MEDIUM_AND_ABOVE = 'BLOCK_MEDIUM_AND_ABOVE', + // Content with NEGLIGIBLE, LOW, and MEDIUM will be allowed. + BLOCK_ONLY_HIGH = 'BLOCK_ONLY_HIGH', + // All content will be allowed. + BLOCK_NONE = 'BLOCK_NONE' +} + +/** + * Probability that a prompt or candidate matches a harm category. + * @public + */ +export enum HarmProbability { + // Probability is unspecified. + HARM_PROBABILITY_UNSPECIFIED = 'HARM_PROBABILITY_UNSPECIFIED', + // Content has a negligible chance of being unsafe. + NEGLIGIBLE = 'NEGLIGIBLE', + // Content has a low chance of being unsafe. + LOW = 'LOW', + // Content has a medium chance of being unsafe. + MEDIUM = 'MEDIUM', + // Content has a high chance of being unsafe. + HIGH = 'HIGH' +} + +/** + * Reason that a prompt was blocked. + * @public + */ +export enum BlockReason { + // A blocked reason was not specified. + BLOCKED_REASON_UNSPECIFIED = 'BLOCKED_REASON_UNSPECIFIED', + // Content was blocked by safety settings. + SAFETY = 'SAFETY', + // Content was blocked, but the reason is uncategorized. + OTHER = 'OTHER' +} + +/** + * Reason that a candidate finished. + * @public + */ +export enum FinishReason { + // Default value. This value is unused. + FINISH_REASON_UNSPECIFIED = 'FINISH_REASON_UNSPECIFIED', + // Natural stop point of the model or provided stop sequence. + STOP = 'STOP', + // The maximum number of tokens as specified in the request was reached. + MAX_TOKENS = 'MAX_TOKENS', + // The candidate content was flagged for safety reasons. + SAFETY = 'SAFETY', + // The candidate content was flagged for recitation reasons. + RECITATION = 'RECITATION', + // Unknown reason. + OTHER = 'OTHER' +} + +/** + * Task type for embedding content. + * @public + */ +export enum TaskType { + TASK_TYPE_UNSPECIFIED = 'TASK_TYPE_UNSPECIFIED', + RETRIEVAL_QUERY = 'RETRIEVAL_QUERY', + RETRIEVAL_DOCUMENT = 'RETRIEVAL_DOCUMENT', + SEMANTIC_SIMILARITY = 'SEMANTIC_SIMILARITY', + CLASSIFICATION = 'CLASSIFICATION', + CLUSTERING = 'CLUSTERING' +} diff --git a/packages/vertexai/src/types/index.ts b/packages/vertexai/src/types/index.ts new file mode 100644 index 00000000000..3782a66cc36 --- /dev/null +++ b/packages/vertexai/src/types/index.ts @@ -0,0 +1,21 @@ +/** + * @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. + */ + +export * from './content'; +export * from './enums'; +export * from './requests'; +export * from './responses'; diff --git a/packages/vertexai/src/types/internal.ts b/packages/vertexai/src/types/internal.ts new file mode 100644 index 00000000000..13b525c68a9 --- /dev/null +++ b/packages/vertexai/src/types/internal.ts @@ -0,0 +1,22 @@ +/** + * @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. + */ + +export interface ApiSettings { + apiKey: string; + project: string; + location: string; +} diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts new file mode 100644 index 00000000000..165990deea9 --- /dev/null +++ b/packages/vertexai/src/types/requests.ts @@ -0,0 +1,238 @@ +/** + * @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. + */ + +import { Content } from './content'; +import { HarmBlockThreshold, HarmCategory, TaskType } from './enums'; + +/** + * Base parameters for a number of methods. + * @public + */ +export interface BaseParams { + safetySettings?: SafetySetting[]; + generationConfig?: GenerationConfig; +} + +/** + * Params passed to {@link GoogleGenerativeAI.getGenerativeModel}. + * @public + */ +export interface ModelParams extends BaseParams { + model: string; + tools?: Tool[]; +} + +/** + * Request sent to `generateContent` endpoint. + * @public + */ +export interface GenerateContentRequest extends BaseParams { + contents: Content[]; + tools?: Tool[]; +} + +/** + * Safety setting that can be sent as part of request parameters. + * @public + */ +export interface SafetySetting { + category: HarmCategory; + threshold: HarmBlockThreshold; +} + +/** + * Config options for content-related requests + * @public + */ +export interface GenerationConfig { + candidateCount?: number; + stopSequences?: string[]; + maxOutputTokens?: number; + temperature?: number; + topP?: number; + topK?: number; +} + +/** + * Params for {@link GenerativeModel.startChat}. + * @public + */ +export interface StartChatParams extends BaseParams { + history?: Content[]; + tools?: Tool[]; +} + +/** + * Params for calling {@link GenerativeModel.countTokens} + * @public + */ +export interface CountTokensRequest { + contents: Content[]; +} + +/** + * Params for calling {@link GenerativeModel.embedContent} + * @public + */ +export interface EmbedContentRequest { + content: Content; + taskType?: TaskType; + title?: string; +} + +/** + * Params for calling {@link GenerativeModel.batchEmbedContents} + * @public + */ +export interface BatchEmbedContentsRequest { + requests: EmbedContentRequest[]; +} + +/** + * Params passed to {@link GoogleGenerativeAI.getGenerativeModel}. + * @public + */ +export interface RequestOptions { + /** + * Request timeout in milliseconds. + */ + timeout?: number; + /** + * Version of API endpoint to call (e.g. "v1" or "v1beta"). If not specified, + * defaults to latest stable version. + */ + apiVersion?: string; +} + +/** + * Defines a tool that model can call to access external knowledge. + * @public + */ +export declare type Tool = FunctionDeclarationsTool; + +/** + * Structured representation of a function declaration as defined by the + * [OpenAPI 3.0 specification](https://spec.openapis.org/oas/v3.0.3). Included + * in this declaration are the function name and parameters. This + * FunctionDeclaration is a representation of a block of code that can be used + * as a Tool by the model and executed by the client. + * @public + */ +export declare interface FunctionDeclaration { + /** + * The name of the function to call. Must start with a letter or an + * underscore. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with + * a max length of 64. + */ + name: string; + /** + * Optional. Description and purpose of the function. Model uses it to decide + * how and whether to call the function. + */ + description?: string; + /** + * Optional. Describes the parameters to this function in JSON Schema Object + * format. Reflects the Open API 3.03 Parameter Object. Parameter names are + * case sensitive. For a function with no parameters, this can be left unset. + */ + parameters?: FunctionDeclarationSchema; +} + +/** + * A FunctionDeclarationsTool is a piece of code that enables the system to + * interact with external systems to perform an action, or set of actions, + * outside of knowledge and scope of the model. + * @public + */ +export declare interface FunctionDeclarationsTool { + /** + * Optional. One or more function declarations + * to be passed to the model along with the current user query. Model may + * decide to call a subset of these functions by populating + * {@link FunctionCall} in the response. User should + * provide a {@link FunctionResponse} for each + * function call in the next turn. Based on the function responses, the model will + * generate the final response back to the user. Maximum 64 function + * declarations can be provided. + */ + functionDeclarations?: FunctionDeclaration[]; +} + +/** + * Contains the list of OpenAPI data types + * as defined by https://swagger.io/docs/specification/data-models/data-types/ + * @public + */ +export enum FunctionDeclarationSchemaType { + /** String type. */ + STRING = 'STRING', + /** Number type. */ + NUMBER = 'NUMBER', + /** Integer type. */ + INTEGER = 'INTEGER', + /** Boolean type. */ + BOOLEAN = 'BOOLEAN', + /** Array type. */ + ARRAY = 'ARRAY', + /** Object type. */ + OBJECT = 'OBJECT' +} + +/** + * Schema for parameters passed to {@link FunctionDeclaration.parameters}. + * @public + */ +export interface FunctionDeclarationSchema { + /** The type of the parameter. */ + type: FunctionDeclarationSchemaType; + /** The format of the parameter. */ + properties: { [k: string]: FunctionDeclarationSchemaProperty }; + /** Optional. Description of the parameter. */ + description?: string; + /** Optional. Array of required parameters. */ + required?: string[]; +} + +/** + * Schema is used to define the format of input/output data. + * Represents a select subset of an OpenAPI 3.0 schema object. + * More fields may be added in the future as needed. + * @public + */ +export interface FunctionDeclarationSchemaProperty { + /** + * Optional. The type of the property. {@link + * FunctionDeclarationSchemaType}. + */ + type?: FunctionDeclarationSchemaType; + /** Optional. The format of the property. */ + format?: string; + /** Optional. The description of the property. */ + description?: string; + /** Optional. Whether the property is nullable. */ + nullable?: boolean; + /** Optional. The items of the property. {@link FunctionDeclarationSchema} */ + items?: FunctionDeclarationSchema; + /** Optional. The enum of the property. */ + enum?: string[]; + /** Optional. Map of {@link FunctionDeclarationSchema}. */ + properties?: { [k: string]: FunctionDeclarationSchema }; + /** Optional. Array of required property. */ + required?: string[]; + /** Optional. The example of the property. */ + example?: unknown; +} diff --git a/packages/vertexai/src/types/responses.ts b/packages/vertexai/src/types/responses.ts new file mode 100644 index 00000000000..5384bc9dfc0 --- /dev/null +++ b/packages/vertexai/src/types/responses.ts @@ -0,0 +1,157 @@ +/** + * @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. + */ + +import { Content, FunctionCall } from './content'; +import { + BlockReason, + FinishReason, + HarmCategory, + HarmProbability +} from './enums'; + +/** + * Result object returned from generateContent() call. + * + * @public + */ +export interface GenerateContentResult { + response: EnhancedGenerateContentResponse; +} + +/** + * Result object returned from generateContentStream() call. + * Iterate over `stream` to get chunks as they come in and/or + * use the `response` promise to get the aggregated response when + * the stream is done. + * + * @public + */ +export interface GenerateContentStreamResult { + stream: AsyncGenerator; + response: Promise; +} + +/** + * Response object wrapped with helper methods. + * + * @public + */ +export interface EnhancedGenerateContentResponse + extends GenerateContentResponse { + /** + * Returns the text string from the response, if available. + * Throws if the prompt or candidate was blocked. + */ + text: () => string; + functionCall: () => FunctionCall | undefined; +} + +/** + * Individual response from {@link GenerativeModel.generateContent} and + * {@link GenerativeModel.generateContentStream}. + * `generateContentStream()` will return one in each chunk until + * the stream is done. + * @public + */ +export interface GenerateContentResponse { + candidates?: GenerateContentCandidate[]; + promptFeedback?: PromptFeedback; +} + +/** + * If the prompt was blocked, this will be populated with `blockReason` and + * the relevant `safetyRatings`. + * @public + */ +export interface PromptFeedback { + blockReason: BlockReason; + safetyRatings: SafetyRating[]; + blockReasonMessage?: string; +} + +/** + * A candidate returned as part of a {@link GenerateContentResponse}. + * @public + */ +export interface GenerateContentCandidate { + index: number; + content: Content; + finishReason?: FinishReason; + finishMessage?: string; + safetyRatings?: SafetyRating[]; + citationMetadata?: CitationMetadata; +} + +/** + * Citation metadata that may be found on a {@link GenerateContentCandidate}. + * @public + */ +export interface CitationMetadata { + citationSources: CitationSource[]; +} + +/** + * A single citation source. + * @public + */ +export interface CitationSource { + startIndex?: number; + endIndex?: number; + uri?: string; + license?: string; +} + +/** + * A safety rating associated with a {@link GenerateContentCandidate} + * @public + */ +export interface SafetyRating { + category: HarmCategory; + probability: HarmProbability; +} + +/** + * Response from calling {@link GenerativeModel.countTokens}. + * @public + */ +export interface CountTokensResponse { + totalTokens: number; +} + +/** + * Response from calling {@link GenerativeModel.embedContent}. + * @public + */ +export interface EmbedContentResponse { + embedding: ContentEmbedding; +} + +/** + * Response from calling {@link GenerativeModel.batchEmbedContents}. + * @public + */ +export interface BatchEmbedContentsResponse { + embeddings: ContentEmbedding[]; +} + +/** + * A single content embedding. + * @public + */ +export interface ContentEmbedding { + values: number[]; +} diff --git a/packages/vertexai/test-utils/base64cat.ts b/packages/vertexai/test-utils/base64cat.ts new file mode 100644 index 00000000000..45325a1bf55 --- /dev/null +++ b/packages/vertexai/test-utils/base64cat.ts @@ -0,0 +1,19 @@ +/** + * @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. + */ + +export const base64Cat = + 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAABJWlDQ1BrQ0dDb2xvclNwYWNlQWRvYmVSR0IxOTk4AAAokWNgYFJILCjIYRJgYMjNKykKcndSiIiMUmB/zsDNwAnE2gwGicnFBY4BAT4MQACjUcG3awyMIPqyLsgsTHm8gCsltTgZSP8B4uzkgqISBgbGDCBbubykAMTuAbJFkrLB7AUgdhHQgUD2FhA7HcI+AVYDYd8BqwkJcgayPwDZfElgNhPILr50CFsAxIbaCwKCjin5SakKIN9rGFpaWmiS6AeCoCS1ogREO+cXVBZlpmeUKDgCQypVwTMvWU9HwcjAyJiBARTuENWfA8HhySh2BiGGAAixORIMDP5LGRhY/iDETHoZGBboMDDwT0WIqRkyMAjoMzDsm5NcWlQGNYaRCWgnIT4AXxVKdgMmGHwAAAFQZVhJZk1NACoAAAAIAAkBDgACAAAARwAAAHoBEgADAAAAAQABAAABGgAFAAAAAQAAAMIBGwAFAAAAAQAAAMoBKAADAAAAAQACAAABMQACAAAACwAAANIBMgACAAAAFAAAAN6CmAACAAAAEwAAAPKHaQAEAAAAAQAAAQYAAAAAUGhvdG9ncmFwaCBmcm9tIFdhbHRlciBDaGFuZG9oYTogVGhlIENhdCBQaG90b2dyYXBoZXIgKEFwZXJ0dXJlLCAyMDE1KQAAAAABLAAAAAEAAAEsAAAAAVBob3RvU2NhcGUAADIwMTU6MDY6MTggMTE6MTM6NTMAwqkgV2FsdGVyIENoYW5kb2hhAAAABJAAAAcAAAAEMDIyMZAEAAIAAAAUAAABPKACAAQAAAABAAABAKADAAQAAAABAAABAAAAAAAyMDE1OjA0OjAxIDE1OjE3OjAzALUWG8IAAAAJcEhZcwAALiMAAC4jAXilP3YAADtdaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBSaWdodHM9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9yaWdodHMvIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgICAgICAgICAgeG1sbnM6eHdudj0iaHR0cDovL25zLnhpbmV0LmNvbS9ucy94aW5ldHNjaGVtYSMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvdGlmZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6ZGVzY3JpcHRpb24+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPlBob3RvZ3JhcGggZnJvbSBXYWx0ZXIgQ2hhbmRvaGE6IFRoZSBDYXQgUGhvdG9ncmFwaGVyIChBcGVydHVyZSwgMjAxNSk8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOmRlc2NyaXB0aW9uPgogICAgICAgICA8ZGM6cmlnaHRzPgogICAgICAgICAgICA8cmRmOkFsdD4KICAgICAgICAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij7CqSBXYWx0ZXIgQ2hhbmRvaGE8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnJpZ2h0cz4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6Q29tcHJlc3Npb24+MTwvdGlmZjpDb21wcmVzc2lvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MzAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4zMDA8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4zMDU3PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6RXhpZlZlcnNpb24+MDIyMTwvZXhpZjpFeGlmVmVyc2lvbj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT42NTUzNTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MjE3MzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDx4bXBSaWdodHM6TWFya2VkPlRydWU8L3htcFJpZ2h0czpNYXJrZWQ+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGhvdG9TY2FwZTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOlJhdGluZz4xPC94bXA6UmF0aW5nPgogICAgICAgICA8eG1wOk1ldGFkYXRhRGF0ZT4yMDE1LTA2LTE4VDExOjEzOjUzLTA0OjAwPC94bXA6TWV0YWRhdGFEYXRlPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxNS0wNC0wMVQxNToxNzowMy0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TGFiZWw+QXBwcm92ZWQ8L3htcDpMYWJlbD4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTUtMDYtMThUMTE6MTM6NTMtMDQ6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8eG1wTU06SGlzdG9yeT4KICAgICAgICAgICAgPHJkZjpTZXE+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTAxVDE1OjE3OjAzLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOkQ2NDkzMDk2MzgyNzY4MTE4NzFGRDg0Mjk3MDE2Mjk3PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmNyZWF0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDQtMDFUMTg6NTg6MDEtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YTI2NDlkMWMtM2YzNy00YzVlLWFmNGMtN2MxMDg1ZTc4Yjc5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBpbWFnZS90aWZmIHRvIGltYWdlL2Vwc2Y8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5kZXJpdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmNvbnZlcnRlZCBmcm9tIGltYWdlL3RpZmYgdG8gaW1hZ2UvZXBzZjwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDQtMDFUMTg6NTg6MDEtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6OGEwOTVhMjMtNTBlMS00ZDA3LWI0YjctNGNhNDA4YzVlODcyPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTAxVDE5OjQ0OjQ2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjlkYzRhZDAyLTgwMzItNDMyZS05YjhlLTk1YThlMTQ1YzJkMDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y29udmVydGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmZyb20gaW1hZ2UvZXBzZiB0byBpbWFnZS90aWZmPC9zdEV2dDpwYXJhbWV0ZXJzPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+ZGVyaXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5jb252ZXJ0ZWQgZnJvbSBpbWFnZS9lcHNmIHRvIGltYWdlL3RpZmY8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTAxVDE5OjQ0OjQ2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjRkZTAwN2U3LTk5MWUtNDc5MS1hOTZkLTE5ZmVhMDllNWI4NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0wMVQyMDo0Njo1Ni0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowMjMyZmIxZC1jYjQ0LTQwOGYtYWE2MC04N2U3MDYyMGNhYmM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93czwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0xOVQwMDoxODozMy0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDpCOUM3OENGNDQ2RTZFNDExOTRFQzkzQjJERjdGRjg2NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y29udmVydGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmZyb20gaW1hZ2UvdGlmZiB0byBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wPC9zdEV2dDpwYXJhbWV0ZXJzPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+ZGVyaXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5jb252ZXJ0ZWQgZnJvbSBpbWFnZS90aWZmIHRvIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3A8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3M8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDQtMTlUMDA6MTg6MzMtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6QkFDNzhDRjQ0NkU2RTQxMTk0RUM5M0IyREY3RkY4NjU8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93czwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0xOVQxMjo0Mjo1OC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowNUZDQzI0NUIxRTZFNDExQjVCMEU3QTI0NTYyMUZGNjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzUuMSBXaW5kb3dzPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTE5VDEyOjQzOjMwLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjA4RkNDMjQ1QjFFNkU0MTFCNUIwRTdBMjQ1NjIxRkY2PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3RpZmY8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5kZXJpdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmNvbnZlcnRlZCBmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvdGlmZjwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93czwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0xOVQxMjo0MzozMC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowOUZDQzI0NUIxRTZFNDExQjVCMEU3QTI0NTYyMUZGNjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIxVDEwOjQ4OjM1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmUzODZlNjQ4LWZmNjYtNDA5NC04NWEyLWY2NjgxZTRiM2I4Mjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIEJyaWRnZSBDQyAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIxOjEwOjQ2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmIwZmJjMzZkLThhMjgtNDU4NC05NTlkLTk5NjFmZDEyMDA5Mjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjIyOjIzLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmM1MzNhNjRlLTk0OTktNDMzOS05MjM4LThhOGY0Nzc3NzQ3YTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjIzOjQyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmRiMjlmZTVlLWQ2ZDAtNDBjZi04Y2RkLTBkYTU2NDgwMTU2YTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDYW1lcmEgUmF3IDguODwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjMyOjMwLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmRkMzFmNTVlLTUyMzctNDA5ZC1hMTk2LTdhM2Q1ZTIwMTM0Mjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDYW1lcmEgUmF3IDguOCAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjMzOjM3LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmUzNjExZWQ3LTI4YmYtNGE5MS1hZDA5LWVhNjc3M2U4Y2YyOTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0yOFQxMDowMTo1MS0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDo1MDViZDMzYS03ZDJiLTRiNzQtOTU1My0xNjIzYjE4MzExMmM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNi0xOFQxMDowMjo1OS0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDpFMzBDRTVCOUMyMTVFNTExQkVDQkU3RTMxNDVCODA4NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDYW1lcmEgUmF3IDguMjwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA2LTE4VDEwOjA0OjQ1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOkE4MUY0NUYxQzIxNUU1MTE4OTE4RkI0OTg3NjBDNzI5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENhbWVyYSBSYXcgOC4yIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA2LTE4VDEwOjA1OjU1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjMzNTk0ZDkyLWNlMmYtNGE0Ny1iNTFmLTRmOTUzNjVmNWJkNTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA2LTE4VDExOjEyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjA3QUE3MDVFQ0MxNUU1MTE5NjEwQjRCN0U1NjM5OEUwPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDYtMThUMTE6MTM6NTMtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MTFBQTcwNUVDQzE1RTUxMTk2MTBCNEI3RTU2Mzk4RTA8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6RDY0OTMwOTYzODI3NjgxMTg3MUZEODQyOTcwMTYyOTc8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkRlcml2ZWRGcm9tIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOkQ2NDkzMDk2MzgyNzY4MTE4NzFGRDg0Mjk3MDE2Mjk3PC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6MDhGQ0MyNDVCMUU2RTQxMUI1QjBFN0EyNDU2MjFGRjY8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6RDY0OTMwOTYzODI3NjgxMTg3MUZEODQyOTcwMTYyOTc8L3N0UmVmOmRvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAg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'; diff --git a/packages/vertexai/test-utils/cat.jpeg b/packages/vertexai/test-utils/cat.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2c21763bad2b0bf81dd3b4c2220e8595d5ee667f GIT binary patch literal 35160 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⁣*j_xe#Q2p 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�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

oHVPob#2|C>H_4WI7;XA?cC0%{_C4G{cOod=0JA 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 { + const list = fs.readdirSync(mockResponseDir); + const lookup: Record = {}; + // eslint-disable-next-line guard-for-in + for (const fileName of list) { + const fullText = fs.readFileSync(join(mockResponseDir, fileName), 'utf-8'); + lookup[fileName] = fullText; + } + let fileText = `// Generated from mocks text files.`; + + fileText += '\n\n'; + fileText += `export const mocksLookup: Record = ${JSON.stringify( + lookup, + null, + 2 + )}`; + fileText += ';\n'; + fs.writeFileSync(join(__dirname, 'mocks-lookup.ts'), fileText, 'utf-8'); +} + +main().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/packages/vertexai/test-utils/mock-response.ts b/packages/vertexai/test-utils/mock-response.ts new file mode 100644 index 00000000000..8332d9eb36e --- /dev/null +++ b/packages/vertexai/test-utils/mock-response.ts @@ -0,0 +1,65 @@ +/** + * @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. + */ + +import { mocksLookup } from './mocks-lookup'; + +/** + * Mock native Response.body + * Streams contents of json file in 20 character chunks + */ +export function getChunkedStream( + input: string, + chunkLength = 20 +): ReadableStream { + const encoder = new TextEncoder(); + let currentChunkStart = 0; + + const stream = new ReadableStream({ + start(controller) { + while (currentChunkStart < input.length) { + const substring = input.slice( + currentChunkStart, + currentChunkStart + chunkLength + ); + currentChunkStart += chunkLength; + const chunk = encoder.encode(substring); + controller.enqueue(chunk); + } + controller.close(); + } + }); + + return stream; +} +export function getMockResponseStreaming( + filename: string, + chunkLength: number = 20 +): Partial { + const fullText = mocksLookup[filename]; + + return { + body: getChunkedStream(fullText, chunkLength) + }; +} + +export function getMockResponse(filename: string): Partial { + const fullText = mocksLookup[filename]; + return { + ok: true, + json: () => Promise.resolve(JSON.parse(fullText)) + }; +} diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt new file mode 100644 index 00000000000..112a84ada1f --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {},"index": 0}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt new file mode 100644 index 00000000000..b73c75cf505 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {"parts": [{"text": "No"}]},"finishReason": "SAFETY","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt new file mode 100644 index 00000000000..58c914af08e --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt @@ -0,0 +1,2 @@ +data: {"promptFeedback": {"blockReason": "SAFETY","safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt new file mode 100644 index 00000000000..1b8c14049c6 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt @@ -0,0 +1,6 @@ +data: {"candidates": [{"content": {"parts": [{"text": "Copyrighted text goes here"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]} + +data: {"candidates": [{"finishReason": "RECITATION","index": 0}]} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt new file mode 100644 index 00000000000..fe662e6ffff --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt @@ -0,0 +1,12 @@ +data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\n\n- **Physical Characteristics:**\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\n\n- **Behavior and Personality:**\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\n - Curious: Cats are curious creatures that love to explore their surroundings.\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": "ls.\n\n- **Health and Care:**\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\n\n**Dogs:**\n\n- **Physical Characteristics:**\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\n\n- **Behavior and Personality:**\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\n\n- **Health and Care:**\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on."}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt new file mode 100644 index 00000000000..a7f5476954e --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {"parts": [{"text": "Cheyenne"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt new file mode 100644 index 00000000000..1b4c4f8db52 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt @@ -0,0 +1,12 @@ +data: {"candidates": [{"content": {"parts": [{"text": "1. **Definition:**\nQuantum mechanics is a fundamental theory in physics that provides"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": " the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": ", and other quantities are quantized, meaning they can exist only in discrete values. \n\n2. **Key Concepts:**\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \n - **Uncertainty Principle:** Proposed by Werner"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \"collapses\" into a single state.\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \n\n3. **Applications:**\n - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} + +data: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \n\n4. **Learning Resources:**\n - **Books:**\n - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe\n - \"Principles of Quantum Mechanics\" by R. Shankar\n - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili\n - **Online Courses:**\n - \"Quantum Mechanics I\" by MIT OpenCourseWare\n - \"Quantum Mechanics for Everyone\" by Coursera\n - \"Quantum Mechanics\" by Stanford Online\n - **Documentaries and Videos:**\n - \"Quantum Mechanics: The Strangest Theory\" (BBC Documentary)\n - \"Quantum Mechanics Explained Simply\" by Veritasium (YouTube Channel)\n - \"What is Quantum Mechanics?\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} + +data: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt new file mode 100644 index 00000000000..ad6cb050da5 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {"parts": [{ "functionCall": { "name": "getTemperature", "args": { "city": "San Jose" } } }]}, "finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt new file mode 100644 index 00000000000..a7531ab2580 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt @@ -0,0 +1,8 @@ +data: { "candidates": [ { "content": { "parts": [ { "text": "秋风瑟瑟,叶落纷纷,\n西风残照,寒" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ], "promptFeedback": { "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } } + +data: { "candidates": [ { "content": { "parts": [ { "text": "霜渐浓。\n枫叶红了,菊花黄了,\n秋雨绵绵,秋意浓浓。\n\n秋夜漫漫,思" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] } + +data: { "candidates": [ { "content": { "parts": [ { "text": "绪万千,\n明月当空,星星眨眼。\n思念远方的亲人,\n祝愿他们幸福安康。\n\n秋天是收获的季节,\n人们忙着收割庄稼,\n为一年的辛劳画上圆满的句号。\n秋天也是团圆的季节" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] } + +data: { "candidates": [ { "content": { "parts": [ { "text": ",\n一家人围坐在一起,\n分享丰收的喜悦,共度美好时光。\n\n秋天是一个美丽的季节,\n它有着独特的韵味,\n让人沉醉其中,流连忘返。\n让我们一起欣赏秋天的美景,\n感受秋天的气息,领悟秋天的哲理。" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] } + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt b/packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt new file mode 100644 index 00000000000..70210e56739 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt @@ -0,0 +1,12 @@ +data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\n\n- **Physical Characteristics:**\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\n\n- **Behavior and Personality:**\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\n - Curious: Cats are curious creatures that love to explore their surroundings.\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": "ls.\n\n- **Health and Care:**\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\n\n**Dogs:**\n\n- **Physical Characteristics:**\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\n\n- **Behavior and Personality:**\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\n\n- **Health and Care:**\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on."}]},"finishReason": "FAKE_ENUM","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM","probability": "NEGLIGIBLE_UNKNOWN_ENUM"}]}]} + diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json b/packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json new file mode 100644 index 00000000000..4e1889660f2 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json @@ -0,0 +1,28 @@ +{ + "candidates": [ + { + "content": {}, + "index": 0 + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json b/packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json new file mode 100644 index 00000000000..6e1e20f734b --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json @@ -0,0 +1,53 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "No" + } + ] + }, + "finishReason": "SAFETY", + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "HIGH" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json b/packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json new file mode 100644 index 00000000000..9dacdc71e7a --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json @@ -0,0 +1,13 @@ +{ + "error": { + "code": 400, + "message": "Request contains an invalid argument.", + "status": "INVALID_ARGUMENT", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.DebugInfo", + "detail": "[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json b/packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json new file mode 100644 index 00000000000..9d2abbb23d6 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json @@ -0,0 +1,23 @@ +{ + "promptFeedback": { + "blockReason": "SAFETY", + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "HIGH" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json new file mode 100644 index 00000000000..46e45165d74 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json @@ -0,0 +1,52 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "1. **Use Freshly Ground Coffee**:\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\n - Use a burr grinder for a consistent grind size.\n\n\n2. **Choose the Right Water**:\n - Use filtered or spring water for the best taste.\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\n\n\n3. **Measure Accurately**:\n - Use a kitchen scale to measure your coffee and water precisely.\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\n\n\n4. **Preheat Your Equipment**:\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\n\n\n5. **Control the Water Temperature**:\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\n\n\n6. **Steep the Coffee**:\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\n\n\n7. **Clean Your Equipment**:\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\n\n\n8. **Experiment with Different Coffee Beans**:\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\n\n\n9. **Store Coffee Properly**:\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\n\n\n10. **Enjoy Freshly Brewed Coffee**:\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing." + } + ] + }, + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json new file mode 100644 index 00000000000..345ec582f7d --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json @@ -0,0 +1,52 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Helena" + } + ] + }, + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-citations.json b/packages/vertexai/test-utils/mock-responses/unary-success-citations.json new file mode 100644 index 00000000000..32ead327047 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-success-citations.json @@ -0,0 +1,64 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "1. **Definition:**\nQuantum mechanics is a fundamental theory in physics that provides the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum, and other quantities are quantized, meaning they can only exist in discrete values. \n\n2. **Key Concepts:**\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \n - **Uncertainty Principle:** Proposed by Werner Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n - **Quantum Superposition:** A quantum system can exist in multiple states simultaneously until it is measured. \n - **Quantum Entanglement:** Two or more particles can become linked in such a way that the state of one affects the state of the others, regardless of the distance between them. \n\n3. **Implications and Applications:**\n - **Atomic and Molecular Structure:** Quantum mechanics explains the structure of atoms, molecules, and chemical bonds. \n - **Quantum Computing:** It enables the development of quantum computers that can solve certain computational problems much faster than classical computers. \n - **Quantum Cryptography:** Quantum principles are used to develop secure communication methods. \n - **Quantum Field Theory (QFT):** A relativistic theory that describes interactions between particles in quantum mechanics. It underpins the Standard Model of Physics. \n - **Quantum Gravity:** Attempts to reconcile quantum mechanics with general relativity to explain the behavior of matter and gravity at very small scales. \n\n4. **Learning Resources:**\n - **Books:**\n - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe \n - \"Principles of Quantum Mechanics\" by R. Shankar\n - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili \n - **Online Courses and Tutorials:**\n - [Quantum Mechanics I](https://www.example.com) on Coursera\n - [MIT OpenCourseWare](https://www.example.com) Quantum Physics I course materials \n - [Khan Academy](https://www.example.com) Quantum Physics video tutorials \n - **Videos and Documentaries:**\n - [Quantum Mechanics - Crash Course Physics](https://www.example.com)\n - [Quantum Mechanics: The Strangest Theory](https://www.example.com)\n - [BBC: The Quantum World with Jim Al-Khalili](https://www.example.com)\n - [NOVA: The Fabric of the Cosmos](https://www.example.com)" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ], + "citationMetadata": { + "citationSources": [ + { + "startIndex": 574, + "endIndex": 705, + "uri": "https://www.example.com", + "license": "" + } + ] + } + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json b/packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json new file mode 100644 index 00000000000..92ced8ce3b8 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json @@ -0,0 +1,52 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "1. **Use Freshly Ground Coffee**:\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\n - Use a burr grinder for a consistent grind size.\n\n\n2. **Choose the Right Water**:\n - Use filtered or spring water for the best taste.\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\n\n\n3. **Measure Accurately**:\n - Use a kitchen scale to measure your coffee and water precisely.\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\n\n\n4. **Preheat Your Equipment**:\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\n\n\n5. **Control the Water Temperature**:\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\n\n\n6. **Steep the Coffee**:\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\n\n\n7. **Clean Your Equipment**:\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\n\n\n8. **Experiment with Different Coffee Beans**:\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\n\n\n9. **Store Coffee Properly**:\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\n\n\n10. **Enjoy Freshly Brewed Coffee**:\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing." + } + ] + }, + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT_ENUM_NEW", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT_LIKE_A_NEW_ENUM", + "probability": "NEGLIGIBLE_NEW_ENUM" + } + ] + } +} diff --git a/packages/vertexai/tsconfig.json b/packages/vertexai/tsconfig.json index a06ed9a374c..ca3b32571f5 100644 --- a/packages/vertexai/tsconfig.json +++ b/packages/vertexai/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../config/tsconfig.base.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", }, "exclude": [ "dist/**/*" From 0338896ad92fec0c27908f0b1d7631edd0e9e31e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 28 Mar 2024 14:17:10 -0700 Subject: [PATCH 03/28] Fix types to match Vertex API and convert functionCall() to functionCalls() (#284) --- packages/vertexai/package.json | 10 ++- packages/vertexai/rollup.config.js | 15 ++++- packages/vertexai/src/methods/count-tokens.ts | 10 ++- .../vertexai/src/methods/embed-content.ts | 67 ------------------- .../src/methods/generate-content.test.ts | 2 +- .../vertexai/src/models/generative-model.ts | 34 +--------- .../vertexai/src/requests/request-helpers.ts | 17 +---- .../vertexai/src/requests/request.test.ts | 10 +++ packages/vertexai/src/requests/request.ts | 27 +++++--- .../src/requests/response-helpers.test.ts | 44 +++++++++++- .../vertexai/src/requests/response-helpers.ts | 26 +++++-- .../src/requests/stream-reader.test.ts | 35 +++++----- packages/vertexai/src/types/content.ts | 21 ++++++ packages/vertexai/src/types/enums.ts | 13 ---- packages/vertexai/src/types/requests.ts | 26 ++----- packages/vertexai/src/types/responses.ts | 51 +++++++------- ...treaming-failure-recitation-no-content.txt | 2 +- .../streaming-success-citations.txt | 6 +- .../unary-success-citations.json | 2 +- scripts/format/license.ts | 9 ++- 20 files changed, 197 insertions(+), 230 deletions(-) delete mode 100644 packages/vertexai/src/methods/embed-content.ts diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index 01190a54e85..f51c323fe5d 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -36,12 +36,10 @@ "build": "rollup -c", "build:deps": "lerna run --scope @firebase/vertexai --include-dependencies build", "dev": "rollup -c -w", - "pretest": "yarn ts-node ./test-utils/convert-mocks.ts", - "test": "run-p --npm-path npm lint test:all", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", - "test:all": "run-p --npm-path npm test:browser test:node", - "test:browser": "karma start --single-run", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.* --config ../../config/mocharc.node.js" + "testsetup": "yarn ts-node ./test-utils/convert-mocks.ts", + "test": "run-p --npm-path npm lint test:browser", + "test:ci": "yarn testsetup && node ../../scripts/run_tests_in_ci.js -s test", + "test:browser": "yarn testsetup && karma start --single-run" }, "peerDependencies": { "@firebase/app": "0.x", diff --git a/packages/vertexai/rollup.config.js b/packages/vertexai/rollup.config.js index add0a061191..f630b522a8e 100644 --- a/packages/vertexai/rollup.config.js +++ b/packages/vertexai/rollup.config.js @@ -52,7 +52,10 @@ const browserBuilds = [ output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], plugins: [ ...es5BuildPlugins, - replace(generateBuildTargetReplaceConfig('esm', 5)) + replace({ + ...generateBuildTargetReplaceConfig('esm', 5), + __PACKAGE_VERSION__: pkg.version + }) ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) }, @@ -65,7 +68,10 @@ const browserBuilds = [ }, plugins: [ ...es2017BuildPlugins, - replace(generateBuildTargetReplaceConfig('esm', 2017)), + replace({ + ...generateBuildTargetReplaceConfig('esm', 2017), + __PACKAGE_VERSION__: pkg.version + }), emitModulePackageFile() ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) @@ -79,7 +85,10 @@ const browserBuilds = [ }, plugins: [ ...es2017BuildPlugins, - replace(generateBuildTargetReplaceConfig('cjs', 2017)) + replace({ + ...generateBuildTargetReplaceConfig('cjs', 2017), + __PACKAGE_VERSION__: pkg.version + }) ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } diff --git a/packages/vertexai/src/methods/count-tokens.ts b/packages/vertexai/src/methods/count-tokens.ts index 5473e9f4db0..fb6808feccc 100644 --- a/packages/vertexai/src/methods/count-tokens.ts +++ b/packages/vertexai/src/methods/count-tokens.ts @@ -29,10 +29,16 @@ export async function countTokens( params: CountTokensRequest, requestOptions?: RequestOptions ): Promise { - const url = new RequestUrl(model, Task.COUNT_TOKENS, apiSettings, false, {}); + const url = new RequestUrl( + model, + Task.COUNT_TOKENS, + apiSettings, + false, + requestOptions + ); const response = await makeRequest( url, - JSON.stringify({ ...params, model }), + JSON.stringify(params), requestOptions ); return response.json(); diff --git a/packages/vertexai/src/methods/embed-content.ts b/packages/vertexai/src/methods/embed-content.ts deleted file mode 100644 index e1f1b2fcb6d..00000000000 --- a/packages/vertexai/src/methods/embed-content.ts +++ /dev/null @@ -1,67 +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. - */ - -import { - BatchEmbedContentsRequest, - BatchEmbedContentsResponse, - EmbedContentRequest, - EmbedContentResponse, - RequestOptions -} from '../types'; -import { RequestUrl, Task, makeRequest } from '../requests/request'; -import { ApiSettings } from '../types/internal'; - -export async function embedContent( - apiSettings: ApiSettings, - model: string, - params: EmbedContentRequest, - requestOptions?: RequestOptions -): Promise { - const url = new RequestUrl(model, Task.EMBED_CONTENT, apiSettings, false, {}); - const response = await makeRequest( - url, - JSON.stringify(params), - requestOptions - ); - return response.json(); -} - -export async function batchEmbedContents( - apiSettings: ApiSettings, - model: string, - params: BatchEmbedContentsRequest, - requestOptions?: RequestOptions -): Promise { - const url = new RequestUrl( - model, - Task.BATCH_EMBED_CONTENTS, - apiSettings, - false, - {} - ); - const requestsWithModel: EmbedContentRequest[] = params.requests.map( - request => { - return { ...request, model }; - } - ); - const response = await makeRequest( - url, - JSON.stringify({ requests: requestsWithModel }), - requestOptions - ); - return response.json(); -} diff --git a/packages/vertexai/src/methods/generate-content.test.ts b/packages/vertexai/src/methods/generate-content.test.ts index ff2666d2924..57d51f586b3 100644 --- a/packages/vertexai/src/methods/generate-content.test.ts +++ b/packages/vertexai/src/methods/generate-content.test.ts @@ -104,7 +104,7 @@ describe('generateContent()', () => { ); expect(result.response.text()).to.include('Quantum mechanics is'); expect( - result.response.candidates?.[0].citationMetadata?.citationSources.length + result.response.candidates?.[0].citationMetadata?.citations.length ).to.equal(1); expect(makeRequestStub).to.be.calledWith( match.instanceOf(request.RequestUrl), diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index 62d67f26e1b..fa0538304b2 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -20,12 +20,8 @@ import { generateContentStream } from '../methods/generate-content'; import { - BatchEmbedContentsRequest, - BatchEmbedContentsResponse, CountTokensRequest, CountTokensResponse, - EmbedContentRequest, - EmbedContentResponse, GenerateContentRequest, GenerateContentResult, GenerateContentStreamResult, @@ -39,11 +35,7 @@ import { } from '../types'; import { ChatSession } from '../methods/chat-session'; import { countTokens } from '../methods/count-tokens'; -import { batchEmbedContents, embedContent } from '../methods/embed-content'; -import { - formatEmbedContentInput, - formatGenerateContentInput -} from '../requests/request-helpers'; +import { formatGenerateContentInput } from '../requests/request-helpers'; import { Vertex } from '../public-types'; import { ERROR_FACTORY, VertexError } from '../errors'; import { ApiSettings } from '../types/internal'; @@ -163,28 +155,4 @@ export class GenerativeModel { const formattedParams = formatGenerateContentInput(request); return countTokens(this._apiSettings, this.model, formattedParams); } - - /** - * Embeds the provided content. - */ - async embedContent( - request: EmbedContentRequest | string | Array - ): Promise { - const formattedParams = formatEmbedContentInput(request); - return embedContent(this._apiSettings, this.model, formattedParams); - } - - /** - * Embeds an array of {@link EmbedContentRequest}s. - */ - async batchEmbedContents( - batchEmbedContentRequest: BatchEmbedContentsRequest - ): Promise { - return batchEmbedContents( - this._apiSettings, - this.model, - batchEmbedContentRequest, - this.requestOptions - ); - } } diff --git a/packages/vertexai/src/requests/request-helpers.ts b/packages/vertexai/src/requests/request-helpers.ts index 7745e4b0c65..96f4ffb1ea7 100644 --- a/packages/vertexai/src/requests/request-helpers.ts +++ b/packages/vertexai/src/requests/request-helpers.ts @@ -15,12 +15,7 @@ * limitations under the License. */ -import { - Content, - EmbedContentRequest, - GenerateContentRequest, - Part -} from '../types'; +import { Content, GenerateContentRequest, Part } from '../types'; import { ERROR_FACTORY, VertexError } from '../errors'; export function formatNewContent( @@ -96,13 +91,3 @@ export function formatGenerateContentInput( return { contents: [content] }; } } - -export function formatEmbedContentInput( - params: EmbedContentRequest | string | Array -): EmbedContentRequest { - if (typeof params === 'string' || Array.isArray(params)) { - const content = formatNewContent(params); - return { content }; - } - return params; -} diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts index f7fa33e74b1..35833d72691 100644 --- a/packages/vertexai/src/requests/request.test.ts +++ b/packages/vertexai/src/requests/request.test.ts @@ -90,6 +90,16 @@ describe('request methods', () => { '/v100omega/projects/my-project/locations/us-central1/models/model-name' ); }); + it('custom baseUrl', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + { baseUrl: 'https://my.special.endpoint' } + ); + expect(url.toString()).to.include('https://my.special.endpoint'); + }); it('non-stream - tunedModels/', async () => { const url = new RequestUrl( 'tunedModels/model-name', diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts index 37e5c183696..a610ec5ba5b 100644 --- a/packages/vertexai/src/requests/request.ts +++ b/packages/vertexai/src/requests/request.ts @@ -18,24 +18,19 @@ import { RequestOptions } from '../types'; import { ERROR_FACTORY, VertexError } from '../errors'; import { ApiSettings } from '../types/internal'; +import { version } from '../../package.json'; -const BASE_URL = 'https://staging-firebaseml.sandbox.googleapis.com'; +const DEFAULT_BASE_URL = 'https://firebaseml.googleapis.com'; export const DEFAULT_API_VERSION = 'v2beta'; -/** - * We can't `require` package.json if this runs on web. We will use rollup to - * swap in the version number here at build time. - */ -const PACKAGE_VERSION = '__PACKAGE_VERSION__'; +const PACKAGE_VERSION = version; const PACKAGE_LOG_HEADER = 'firebase-vertexai-js'; export enum Task { GENERATE_CONTENT = 'generateContent', STREAM_GENERATE_CONTENT = 'streamGenerateContent', - COUNT_TOKENS = 'countTokens', - EMBED_CONTENT = 'embedContent', - BATCH_EMBED_CONTENTS = 'batchEmbedContents' + COUNT_TOKENS = 'countTokens' } export class RequestUrl { @@ -48,7 +43,8 @@ export class RequestUrl { ) {} toString(): string { const apiVersion = this.requestOptions?.apiVersion || DEFAULT_API_VERSION; - let url = `${BASE_URL}/${apiVersion}`; + const baseUrl = this.requestOptions?.baseUrl || DEFAULT_BASE_URL; + let url = `${baseUrl}/${apiVersion}`; url += `/projects/${this.apiSettings.project}`; url += `/locations/${this.apiSettings.location}`; url += `/${this.model}`; @@ -58,6 +54,17 @@ export class RequestUrl { } return url; } + + /** + * If the model needs to be passed to the backend, it needs to + * include project and location path. + */ + get fullModelString(): string { + let modelString = `projects/${this.apiSettings.project}`; + modelString += `/locations/${this.apiSettings.location}`; + modelString += `/${this.model}`; + return modelString; + } } /** diff --git a/packages/vertexai/src/requests/response-helpers.test.ts b/packages/vertexai/src/requests/response-helpers.test.ts index 1244fb10d67..79bc6f01d6f 100644 --- a/packages/vertexai/src/requests/response-helpers.test.ts +++ b/packages/vertexai/src/requests/response-helpers.test.ts @@ -39,6 +39,7 @@ const fakeResponseText: GenerateContentResponse = { } ] }; + const fakeResponseFunctionCall: GenerateContentResponse = { candidates: [ { @@ -61,6 +62,38 @@ const fakeResponseFunctionCall: GenerateContentResponse = { ] }; +const fakeResponseFunctionCalls: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [ + { + functionCall: { + name: 'find_theaters', + args: { + location: 'Mountain View, CA', + movie: 'Barbie' + } + } + }, + { + functionCall: { + name: 'find_times', + args: { + location: 'Mountain View, CA', + movie: 'Barbie', + time: '20:00' + } + } + } + ] + } + } + ] +}; + const badFakeResponse: GenerateContentResponse = { promptFeedback: { blockReason: BlockReason.SAFETY, @@ -79,9 +112,16 @@ describe('response-helpers methods', () => { }); it('good response functionCall', async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCall); - expect(enhancedResponse.functionCall()).to.deep.equal( + expect(enhancedResponse.functionCalls()).to.deep.equal([ fakeResponseFunctionCall.candidates?.[0].content.parts[0].functionCall - ); + ]); + }); + it('good response functionCalls', async () => { + const enhancedResponse = addHelpers(fakeResponseFunctionCalls); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + fakeResponseFunctionCalls.candidates?.[0].content.parts[0].functionCall, + fakeResponseFunctionCalls.candidates?.[0].content.parts[1].functionCall + ]); }); it('bad response safety', async () => { const enhancedResponse = addHelpers(badFakeResponse); diff --git a/packages/vertexai/src/requests/response-helpers.ts b/packages/vertexai/src/requests/response-helpers.ts index 6de39fb8639..17a0071b008 100644 --- a/packages/vertexai/src/requests/response-helpers.ts +++ b/packages/vertexai/src/requests/response-helpers.ts @@ -55,12 +55,12 @@ export function addHelpers( } return ''; }; - (response as EnhancedGenerateContentResponse).functionCall = () => { + (response as EnhancedGenerateContentResponse).functionCalls = () => { if (response.candidates && response.candidates.length > 0) { if (response.candidates.length > 1) { console.warn( `This response had ${response.candidates.length} ` + - `candidates. Returning function call from the first candidate only. ` + + `candidates. Returning function calls from the first candidate only. ` + `Access response.candidates directly to use the other candidates.` ); } @@ -70,7 +70,7 @@ export function addHelpers( response }); } - return getFunctionCall(response); + return getFunctionCalls(response); } else if (response.promptFeedback) { throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { message: `Function call not available. ${formatBlockErrorMessage( @@ -98,12 +98,24 @@ export function getText(response: GenerateContentResponse): string { } /** - * Returns {@link FunctionCall} associated with first candidate. + * Returns {@link FunctionCall}s associated with first candidate. */ -export function getFunctionCall( +export function getFunctionCalls( response: GenerateContentResponse -): FunctionCall | undefined { - return response.candidates?.[0].content?.parts?.[0]?.functionCall; +): FunctionCall[] | undefined { + const functionCalls: FunctionCall[] = []; + if (response.candidates?.[0].content?.parts) { + for (const part of response.candidates?.[0].content?.parts) { + if (part.functionCall) { + functionCalls.push(part.functionCall); + } + } + } + if (functionCalls.length > 0) { + return functionCalls; + } else { + return undefined; + } } const badFinishReasons = [FinishReason.RECITATION, FinishReason.SAFETY]; diff --git a/packages/vertexai/src/requests/stream-reader.test.ts b/packages/vertexai/src/requests/stream-reader.test.ts index 34fef7a0265..e942bb9f2b6 100644 --- a/packages/vertexai/src/requests/stream-reader.test.ts +++ b/packages/vertexai/src/requests/stream-reader.test.ts @@ -120,17 +120,21 @@ describe('processStream', () => { const result = processStream(fakeResponse as Response); for await (const response of result.stream) { expect(response.text()).to.be.empty; - expect(response.functionCall()).to.be.deep.equal({ - name: 'getTemperature', - args: { city: 'San Jose' } - }); + expect(response.functionCalls()).to.be.deep.equal([ + { + name: 'getTemperature', + args: { city: 'San Jose' } + } + ]); } const aggregatedResponse = await result.response; expect(aggregatedResponse.text()).to.be.empty; - expect(aggregatedResponse.functionCall()).to.be.deep.equal({ - name: 'getTemperature', - args: { city: 'San Jose' } - }); + expect(aggregatedResponse.functionCalls()).to.be.deep.equal([ + { + name: 'getTemperature', + args: { city: 'San Jose' } + } + ]); }); it('candidate had finishReason', async () => { const fakeResponse = getMockResponseStreaming( @@ -202,8 +206,7 @@ describe('processStream', () => { const aggregatedResponse = await result.response; expect(aggregatedResponse.text()).to.include('Quantum mechanics is'); expect( - aggregatedResponse.candidates?.[0].citationMetadata?.citationSources - .length + aggregatedResponse.candidates?.[0].citationMetadata?.citations.length ).to.equal(2); let foundCitationMetadata = false; for await (const response of result.stream) { @@ -284,7 +287,7 @@ describe('aggregateResponses', () => { } ], citationMetadata: { - citationSources: [ + citations: [ { startIndex: 0, endIndex: 20, @@ -322,7 +325,7 @@ describe('aggregateResponses', () => { } ], citationMetadata: { - citationSources: [ + citations: [ { startIndex: 0, endIndex: 20, @@ -385,15 +388,15 @@ describe('aggregateResponses', () => { ); }); - it('collects all citationSources into one array', () => { + it('collects all citations into one array', () => { expect( - response.candidates?.[0].citationMetadata?.citationSources.length + response.candidates?.[0].citationMetadata?.citations.length ).to.equal(2); expect( - response.candidates?.[0].citationMetadata?.citationSources[0].startIndex + response.candidates?.[0].citationMetadata?.citations[0].startIndex ).to.equal(0); expect( - response.candidates?.[0].citationMetadata?.citationSources[1].startIndex + response.candidates?.[0].citationMetadata?.citations[1].startIndex ).to.equal(150); }); }); diff --git a/packages/vertexai/src/types/content.ts b/packages/vertexai/src/types/content.ts index 1be3b276ea7..c626ce75fc9 100644 --- a/packages/vertexai/src/types/content.ts +++ b/packages/vertexai/src/types/content.ts @@ -56,6 +56,27 @@ export interface InlineDataPart { inlineData: GenerativeContentBlob; functionCall?: never; functionResponse?: never; + /** + * Applicable if `inlineData` is a video. + */ + videoMetadata?: VideoMetadata; +} + +/** + * Describes the input video content. + * @public + */ +export interface VideoMetadata { + /** + * The start offset of the video in + * protobuf {@link https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping | Duration} format. + */ + startOffset: string; + /** + * The end offset of the video in + * protobuf {@link https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping | Duration} format. + */ + endOffset: string; } /** diff --git a/packages/vertexai/src/types/enums.ts b/packages/vertexai/src/types/enums.ts index b08d929bede..cdb8e4bca79 100644 --- a/packages/vertexai/src/types/enums.ts +++ b/packages/vertexai/src/types/enums.ts @@ -104,16 +104,3 @@ export enum FinishReason { // Unknown reason. OTHER = 'OTHER' } - -/** - * Task type for embedding content. - * @public - */ -export enum TaskType { - TASK_TYPE_UNSPECIFIED = 'TASK_TYPE_UNSPECIFIED', - RETRIEVAL_QUERY = 'RETRIEVAL_QUERY', - RETRIEVAL_DOCUMENT = 'RETRIEVAL_DOCUMENT', - SEMANTIC_SIMILARITY = 'SEMANTIC_SIMILARITY', - CLASSIFICATION = 'CLASSIFICATION', - CLUSTERING = 'CLUSTERING' -} diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index 165990deea9..a1092903f0f 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -16,7 +16,7 @@ */ import { Content } from './content'; -import { HarmBlockThreshold, HarmCategory, TaskType } from './enums'; +import { HarmBlockThreshold, HarmCategory } from './enums'; /** * Base parameters for a number of methods. @@ -85,25 +85,7 @@ export interface CountTokensRequest { } /** - * Params for calling {@link GenerativeModel.embedContent} - * @public - */ -export interface EmbedContentRequest { - content: Content; - taskType?: TaskType; - title?: string; -} - -/** - * Params for calling {@link GenerativeModel.batchEmbedContents} - * @public - */ -export interface BatchEmbedContentsRequest { - requests: EmbedContentRequest[]; -} - -/** - * Params passed to {@link GoogleGenerativeAI.getGenerativeModel}. + * Params passed to {@link getGenerativeModel}. * @public */ export interface RequestOptions { @@ -116,6 +98,10 @@ export interface RequestOptions { * defaults to latest stable version. */ apiVersion?: string; + /** + * Base url for endpoint. Defaults to https://firebaseml.googleapis.com + */ + baseUrl?: string; } /** diff --git a/packages/vertexai/src/types/responses.ts b/packages/vertexai/src/types/responses.ts index 5384bc9dfc0..bdba5d28f23 100644 --- a/packages/vertexai/src/types/responses.ts +++ b/packages/vertexai/src/types/responses.ts @@ -57,7 +57,7 @@ export interface EnhancedGenerateContentResponse * Throws if the prompt or candidate was blocked. */ text: () => string; - functionCall: () => FunctionCall | undefined; + functionCalls: () => FunctionCall[] | undefined; } /** @@ -70,6 +70,13 @@ export interface EnhancedGenerateContentResponse export interface GenerateContentResponse { candidates?: GenerateContentCandidate[]; promptFeedback?: PromptFeedback; + usageMetadata?: UsageMetadata; +} + +export interface UsageMetadata { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; } /** @@ -101,18 +108,30 @@ export interface GenerateContentCandidate { * @public */ export interface CitationMetadata { - citationSources: CitationSource[]; + citations: Citation[]; } /** - * A single citation source. + * A single citation. * @public */ -export interface CitationSource { +export interface Citation { startIndex?: number; endIndex?: number; uri?: string; license?: string; + title?: string; + publicationDate?: Date; +} + +/** + * Protobuf google.type.Date + * @public + */ +export interface Date { + year: number; + month: number; + day: number; } /** @@ -131,27 +150,3 @@ export interface SafetyRating { export interface CountTokensResponse { totalTokens: number; } - -/** - * Response from calling {@link GenerativeModel.embedContent}. - * @public - */ -export interface EmbedContentResponse { - embedding: ContentEmbedding; -} - -/** - * Response from calling {@link GenerativeModel.batchEmbedContents}. - * @public - */ -export interface BatchEmbedContentsResponse { - embeddings: ContentEmbedding[]; -} - -/** - * A single content embedding. - * @public - */ -export interface ContentEmbedding { - values: number[]; -} diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt index 1b8c14049c6..05a296d60f0 100644 --- a/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt @@ -1,6 +1,6 @@ data: {"candidates": [{"content": {"parts": [{"text": "Copyrighted text goes here"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} -data: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]} +data: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]} data: {"candidates": [{"finishReason": "RECITATION","index": 0}]} diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt index 1b4c4f8db52..56c8bae95e9 100644 --- a/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt @@ -4,9 +4,9 @@ data: {"candidates": [{"content": {"parts": [{"text": " the foundation for under data: {"candidates": [{"content": {"parts": [{"text": ", and other quantities are quantized, meaning they can exist only in discrete values. \n\n2. **Key Concepts:**\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \n - **Uncertainty Principle:** Proposed by Werner"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} -data: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \"collapses\" into a single state.\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \n\n3. **Applications:**\n - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} +data: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \"collapses\" into a single state.\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \n\n3. **Applications:**\n - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} -data: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \n\n4. **Learning Resources:**\n - **Books:**\n - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe\n - \"Principles of Quantum Mechanics\" by R. Shankar\n - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili\n - **Online Courses:**\n - \"Quantum Mechanics I\" by MIT OpenCourseWare\n - \"Quantum Mechanics for Everyone\" by Coursera\n - \"Quantum Mechanics\" by Stanford Online\n - **Documentaries and Videos:**\n - \"Quantum Mechanics: The Strangest Theory\" (BBC Documentary)\n - \"Quantum Mechanics Explained Simply\" by Veritasium (YouTube Channel)\n - \"What is Quantum Mechanics?\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} +data: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \n\n4. **Learning Resources:**\n - **Books:**\n - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe\n - \"Principles of Quantum Mechanics\" by R. Shankar\n - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili\n - **Online Courses:**\n - \"Quantum Mechanics I\" by MIT OpenCourseWare\n - \"Quantum Mechanics for Everyone\" by Coursera\n - \"Quantum Mechanics\" by Stanford Online\n - **Documentaries and Videos:**\n - \"Quantum Mechanics: The Strangest Theory\" (BBC Documentary)\n - \"Quantum Mechanics Explained Simply\" by Veritasium (YouTube Channel)\n - \"What is Quantum Mechanics?\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} -data: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} +data: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-citations.json b/packages/vertexai/test-utils/mock-responses/unary-success-citations.json index 32ead327047..9d1a7939ed2 100644 --- a/packages/vertexai/test-utils/mock-responses/unary-success-citations.json +++ b/packages/vertexai/test-utils/mock-responses/unary-success-citations.json @@ -30,7 +30,7 @@ } ], "citationMetadata": { - "citationSources": [ + "citations": [ { "startIndex": 574, "endIndex": 705, diff --git a/scripts/format/license.ts b/scripts/format/license.ts index 10de501165f..e5672ef41c3 100644 --- a/scripts/format/license.ts +++ b/scripts/format/license.ts @@ -81,7 +81,14 @@ export async function doLicense(changedFiles?: string[]) { filesToChange = await new Promise(resolve => { glob( '+(packages|repo-scripts)/**/*.+(ts|js)', - { ignore: ['**/node_modules/**', './node_modules/**', '**/dist/**'] }, + { + ignore: [ + '**/node_modules/**', + './node_modules/**', + '**/dist/**', + '**/mocks-lookup.ts' + ] + }, (err, res) => resolve(res) ); }); From 534d8950ef952e2179666e8a785f5d56390184dc Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 29 Mar 2024 10:59:32 -0700 Subject: [PATCH 04/28] Fix and update location field, remove esm5 bundle (#291) --- packages/vertexai/package.json | 2 -- packages/vertexai/rollup.config.js | 12 ---------- packages/vertexai/src/api.test.ts | 2 +- packages/vertexai/src/api.ts | 15 +++++++------ packages/vertexai/src/constants.ts | 2 +- packages/vertexai/src/factory.ts | 6 ++--- packages/vertexai/src/index.ts | 4 ++-- .../src/models/generative-model.test.ts | 2 +- .../vertexai/src/models/generative-model.ts | 2 +- packages/vertexai/src/public-types.ts | 4 ++-- packages/vertexai/test-utils/mocks-lookup.ts | 22 +++++++++++++++++++ 11 files changed, 41 insertions(+), 32 deletions(-) create mode 100644 packages/vertexai/test-utils/mocks-lookup.ts diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index f51c323fe5d..8d18c7bbdd4 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -10,7 +10,6 @@ "main": "dist/index.cjs.js", "browser": "dist/esm/index.esm2017.js", "module": "dist/esm/index.esm2017.js", - "esm5": "dist/index.esm5.js", "exports": { ".": { "types": "./dist/src/index.d.ts", @@ -18,7 +17,6 @@ "require": "./dist/index.cjs.js", "import": "./dist/esm/index.esm2017.js" }, - "esm5": "./dist/index.esm5.js", "browser": { "require": "./dist/index.cjs.js", "import": "./dist/esm/index.esm2017.js" diff --git a/packages/vertexai/rollup.config.js b/packages/vertexai/rollup.config.js index f630b522a8e..6e99c03e913 100644 --- a/packages/vertexai/rollup.config.js +++ b/packages/vertexai/rollup.config.js @@ -47,18 +47,6 @@ const es2017BuildPlugins = [ ]; const browserBuilds = [ - { - input: 'src/index.ts', - output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], - plugins: [ - ...es5BuildPlugins, - replace({ - ...generateBuildTargetReplaceConfig('esm', 5), - __PACKAGE_VERSION__: pkg.version - }) - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, { input: 'src/index.ts', output: { diff --git a/packages/vertexai/src/api.test.ts b/packages/vertexai/src/api.test.ts index b8a657c8a8f..efc2705921a 100644 --- a/packages/vertexai/src/api.test.ts +++ b/packages/vertexai/src/api.test.ts @@ -30,7 +30,7 @@ const fakeVertex: Vertex = { projectId: 'my-project' } }, - region: 'us-central1' + location: 'us-central1' }; describe('Top level API', () => { diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index f9593dfd01d..30c3fee0191 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -20,7 +20,7 @@ import { Provider } from '@firebase/component'; import { getModularInstance } from '@firebase/util'; import { VERTEX_TYPE } from './constants'; import { VertexService } from './factory'; -import { Vertex } from './public-types'; +import { Vertex, VertexOptions } from './public-types'; import { ERROR_FACTORY, VertexError } from './errors'; import { ModelParams, RequestOptions } from './types'; import { GenerativeModel } from './models/generative-model'; @@ -42,16 +42,17 @@ declare module '@firebase/component' { * * @param app - The {@link @firebase/app#FirebaseApp} to use. */ -export function getVertex(app: FirebaseApp = getApp()): Vertex { +export function getVertex( + app: FirebaseApp = getApp(), + options?: VertexOptions +): Vertex { app = getModularInstance(app); // Dependencies const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); - if (vertexProvider.isInitialized()) { - return vertexProvider.getImmediate(); - } - - return vertexProvider.initialize(); + return vertexProvider.getImmediate({ + identifier: options?.location || 'DEFAULT' + }); } export function getGenerativeModel( diff --git a/packages/vertexai/src/constants.ts b/packages/vertexai/src/constants.ts index f947f28e412..899fa83fdff 100644 --- a/packages/vertexai/src/constants.ts +++ b/packages/vertexai/src/constants.ts @@ -17,4 +17,4 @@ export const VERTEX_TYPE = 'vertex'; -export const DEFAULT_REGION = 'us-central1'; +export const DEFAULT_LOCATION = 'us-central1'; diff --git a/packages/vertexai/src/factory.ts b/packages/vertexai/src/factory.ts index f071497aad0..b8aac6827ca 100644 --- a/packages/vertexai/src/factory.ts +++ b/packages/vertexai/src/factory.ts @@ -22,7 +22,7 @@ import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; import { Provider } from '@firebase/component'; -import { DEFAULT_REGION } from './constants'; +import { DEFAULT_LOCATION } from './constants'; export function factory( app: FirebaseApp, @@ -34,7 +34,7 @@ export function factory( export class VertexService implements Vertex, _FirebaseService { appCheck: FirebaseAppCheckInternal | null; - region: string; + location: string; constructor( public app: FirebaseApp, @@ -43,7 +43,7 @@ export class VertexService implements Vertex, _FirebaseService { ) { const appCheck = appCheckProvider?.getImmediate({ optional: true }); this.appCheck = appCheck || null; - this.region = this.options?.region || DEFAULT_REGION; + this.location = this.options?.location || DEFAULT_LOCATION; } _delete(): Promise { diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index 682d1298710..ca6f3de1088 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -37,11 +37,11 @@ function registerVertex(): void { _registerComponent( new Component( VERTEX_TYPE, - (container, { instanceIdentifier: region }) => { + (container, { instanceIdentifier: location }) => { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app').getImmediate(); const appCheckProvider = container.getProvider('app-check-internal'); - return factory(app, appCheckProvider, { region }); + return factory(app, appCheckProvider, { location }); }, ComponentType.PUBLIC ).setMultipleInstances(true) diff --git a/packages/vertexai/src/models/generative-model.test.ts b/packages/vertexai/src/models/generative-model.test.ts index b9e749ba2e2..f3623960872 100644 --- a/packages/vertexai/src/models/generative-model.test.ts +++ b/packages/vertexai/src/models/generative-model.test.ts @@ -27,7 +27,7 @@ const fakeVertex: Vertex = { projectId: 'my-project' } }, - region: 'us-central1' + location: 'us-central1' }; describe('GenerativeModel', () => { diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index fa0538304b2..c9b53e08c9b 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -65,7 +65,7 @@ export class GenerativeModel { this._apiSettings = { apiKey: vertex.app.options.apiKey, project: vertex.app.options.projectId, - location: vertex.region + location: vertex.location }; } if (modelParams.model.includes('/')) { diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index 63df8c527fa..8724759e00a 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -28,9 +28,9 @@ export interface Vertex { * The {@link @firebase/app#FirebaseApp} this {@link Vertex} instance is associated with. */ app: FirebaseApp; - region: string; + location: string; } export interface VertexOptions { - region?: string; + location?: string; } diff --git a/packages/vertexai/test-utils/mocks-lookup.ts b/packages/vertexai/test-utils/mocks-lookup.ts new file mode 100644 index 00000000000..6cd80afb252 --- /dev/null +++ b/packages/vertexai/test-utils/mocks-lookup.ts @@ -0,0 +1,22 @@ +// Generated from mocks text files. + +export const mocksLookup: Record = { + "streaming-failure-empty-content.txt": "data: {\"candidates\": [{\"content\": {},\"index\": 0}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", + "streaming-failure-finish-reason-safety.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"No\"}]},\"finishReason\": \"SAFETY\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"HIGH\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", + "streaming-failure-prompt-blocked-safety.txt": "data: {\"promptFeedback\": {\"blockReason\": \"SAFETY\",\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"HIGH\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", + "streaming-failure-recitation-no-content.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"Copyrighted text goes here\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"More copyrighted text\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"LOW\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 30,\"endIndex\": 179,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\ndata: {\"candidates\": [{\"finishReason\": \"RECITATION\",\"index\": 0}]}\r\n\r\n", + "streaming-success-basic-reply-long.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on.\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\n", + "streaming-success-basic-reply-short.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"Cheyenne\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", + "streaming-success-citations.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \", and other quantities are quantized, meaning they can exist only in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \\\"collapses\\\" into a single state.\\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \\n\\n3. **Applications:**\\n - **Quantum Computing:** It explores the use of quantum\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 574,\"endIndex\": 705,\"uri\": \"https://www.example.com\",\"license\": \"\"},{\"startIndex\": 899,\"endIndex\": 1026,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \\n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \\n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \\n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\\"Quantum Mechanics for Mathematicians\\\" by James Glimm and Arthur Jaffe\\n - \\\"Principles of Quantum Mechanics\\\" by R. Shankar\\n - \\\"Quantum Mechanics: Concepts and Applications\\\" by Nouredine Zettili\\n - **Online Courses:**\\n - \\\"Quantum Mechanics I\\\" by MIT OpenCourseWare\\n - \\\"Quantum Mechanics for Everyone\\\" by Coursera\\n - \\\"Quantum Mechanics\\\" by Stanford Online\\n - **Documentaries and Videos:**\\n - \\\"Quantum Mechanics: The Strangest Theory\\\" (BBC Documentary)\\n - \\\"Quantum Mechanics Explained Simply\\\" by Veritasium (YouTube Channel)\\n - \\\"What is Quantum Mechanics?\\\" by Minute\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 574,\"endIndex\": 705,\"uri\": \"https://www.example.com\",\"license\": \"\"},{\"startIndex\": 899,\"endIndex\": 1026,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"Physics (YouTube Channel)\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 574,\"endIndex\": 705,\"uri\": \"https://www.example.com\",\"license\": \"\"},{\"startIndex\": 899,\"endIndex\": 1026,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\n", + "streaming-success-function-call-short.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{ \"functionCall\": { \"name\": \"getTemperature\", \"args\": { \"city\": \"San Jose\" } } }]}, \"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", + "streaming-success-utf8.txt": "data: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \"秋风瑟瑟,叶落纷纷,\\n西风残照,寒\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ], \"promptFeedback\": { \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } }\r\n\r\ndata: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \"霜渐浓。\\n枫叶红了,菊花黄了,\\n秋雨绵绵,秋意浓浓。\\n\\n秋夜漫漫,思\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ] }\r\n\r\ndata: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \"绪万千,\\n明月当空,星星眨眼。\\n思念远方的亲人,\\n祝愿他们幸福安康。\\n\\n秋天是收获的季节,\\n人们忙着收割庄稼,\\n为一年的辛劳画上圆满的句号。\\n秋天也是团圆的季节\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ] }\r\n\r\ndata: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \",\\n一家人围坐在一起,\\n分享丰收的喜悦,共度美好时光。\\n\\n秋天是一个美丽的季节,\\n它有着独特的韵味,\\n让人沉醉其中,流连忘返。\\n让我们一起欣赏秋天的美景,\\n感受秋天的气息,领悟秋天的哲理。\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ] }\r\n\r\n", + "streaming-unknown-enum.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on.\"}]},\"finishReason\": \"FAKE_ENUM\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM\",\"probability\": \"NEGLIGIBLE_UNKNOWN_ENUM\"}]}]}\r\n\r\n", + "unary-failure-empty-content.json": "{\n \"candidates\": [\n {\n \"content\": {},\n \"index\": 0\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", + "unary-failure-finish-reason-safety.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"No\"\n }\n ]\n },\n \"finishReason\": \"SAFETY\",\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"HIGH\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", + "unary-failure-image-rejected.json": "{\n \"error\": {\n \"code\": 400,\n \"message\": \"Request contains an invalid argument.\",\n \"status\": \"INVALID_ARGUMENT\",\n \"details\": [\n {\n \"@type\": \"type.googleapis.com/google.rpc.DebugInfo\",\n \"detail\": \"[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short\"\n }\n ]\n }\n}\n", + "unary-failure-prompt-blocked-safety.json": "{\n \"promptFeedback\": {\n \"blockReason\": \"SAFETY\",\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"HIGH\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", + "unary-success-basic-reply-long.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing.\"\n }\n ]\n },\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", + "unary-success-basic-reply-short.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"Helena\"\n }\n ]\n },\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", + "unary-success-citations.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum, and other quantities are quantized, meaning they can only exist in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Quantum Superposition:** A quantum system can exist in multiple states simultaneously until it is measured. \\n - **Quantum Entanglement:** Two or more particles can become linked in such a way that the state of one affects the state of the others, regardless of the distance between them. \\n\\n3. **Implications and Applications:**\\n - **Atomic and Molecular Structure:** Quantum mechanics explains the structure of atoms, molecules, and chemical bonds. \\n - **Quantum Computing:** It enables the development of quantum computers that can solve certain computational problems much faster than classical computers. \\n - **Quantum Cryptography:** Quantum principles are used to develop secure communication methods. \\n - **Quantum Field Theory (QFT):** A relativistic theory that describes interactions between particles in quantum mechanics. It underpins the Standard Model of Physics. \\n - **Quantum Gravity:** Attempts to reconcile quantum mechanics with general relativity to explain the behavior of matter and gravity at very small scales. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\\"Quantum Mechanics for Mathematicians\\\" by James Glimm and Arthur Jaffe \\n - \\\"Principles of Quantum Mechanics\\\" by R. Shankar\\n - \\\"Quantum Mechanics: Concepts and Applications\\\" by Nouredine Zettili \\n - **Online Courses and Tutorials:**\\n - [Quantum Mechanics I](https://www.example.com) on Coursera\\n - [MIT OpenCourseWare](https://www.example.com) Quantum Physics I course materials \\n - [Khan Academy](https://www.example.com) Quantum Physics video tutorials \\n - **Videos and Documentaries:**\\n - [Quantum Mechanics - Crash Course Physics](https://www.example.com)\\n - [Quantum Mechanics: The Strangest Theory](https://www.example.com)\\n - [BBC: The Quantum World with Jim Al-Khalili](https://www.example.com)\\n - [NOVA: The Fabric of the Cosmos](https://www.example.com)\"\n }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": \"STOP\",\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ],\n \"citationMetadata\": {\n \"citations\": [\n {\n \"startIndex\": 574,\n \"endIndex\": 705,\n \"uri\": \"https://www.example.com\",\n \"license\": \"\"\n }\n ]\n }\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", + "unary-unknown-enum.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing.\"\n }\n ]\n },\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT_ENUM_NEW\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT_LIKE_A_NEW_ENUM\",\n \"probability\": \"NEGLIGIBLE_NEW_ENUM\"\n }\n ]\n }\n}\n" +}; From 1de23833a7d7274baccef294af151cb0a9278ecd Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 2 Apr 2024 10:39:39 -0700 Subject: [PATCH 05/28] should use DEFAULT_LOCATION (#292) --- packages/vertexai/src/api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 30c3fee0191..8bd196ec7f5 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -18,7 +18,7 @@ import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Provider } from '@firebase/component'; import { getModularInstance } from '@firebase/util'; -import { VERTEX_TYPE } from './constants'; +import { DEFAULT_LOCATION, VERTEX_TYPE } from './constants'; import { VertexService } from './factory'; import { Vertex, VertexOptions } from './public-types'; import { ERROR_FACTORY, VertexError } from './errors'; @@ -51,7 +51,7 @@ export function getVertex( const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); return vertexProvider.getImmediate({ - identifier: options?.location || 'DEFAULT' + identifier: options?.location || DEFAULT_LOCATION }); } From 6561882f461d98f032d9417162cb1e9c6abdbd06 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 2 Apr 2024 12:13:10 -0700 Subject: [PATCH 06/28] Add logging headers (#293) --- packages/app/src/constants.ts | 2 ++ packages/vertexai/src/constants.ts | 10 +++++++++ .../vertexai/src/requests/request.test.ts | 3 ++- packages/vertexai/src/requests/request.ts | 21 ++++++++++--------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/app/src/constants.ts b/packages/app/src/constants.ts index e251c75647b..92102192e93 100644 --- a/packages/app/src/constants.ts +++ b/packages/app/src/constants.ts @@ -38,6 +38,7 @@ import { name as remoteConfigCompatName } from '../../../packages/remote-config- import { name as storageName } from '../../../packages/storage/package.json'; import { name as storageCompatName } from '../../../packages/storage-compat/package.json'; import { name as firestoreName } from '../../../packages/firestore/package.json'; +import { name as vertexName } from '../../../packages/vertexai/package.json'; import { name as firestoreCompatName } from '../../../packages/firestore-compat/package.json'; import { name as packageName } from '../../../packages/firebase/package.json'; @@ -73,6 +74,7 @@ export const PLATFORM_LOG_STRING = { [storageCompatName]: 'fire-gcs-compat', [firestoreName]: 'fire-fst', [firestoreCompatName]: 'fire-fst-compat', + [vertexName]: 'fire-vertex', 'fire-js': 'fire-js', // Platform identifier for JS SDK. [packageName]: 'fire-js-all' } as const; diff --git a/packages/vertexai/src/constants.ts b/packages/vertexai/src/constants.ts index 899fa83fdff..e4bb26baa1d 100644 --- a/packages/vertexai/src/constants.ts +++ b/packages/vertexai/src/constants.ts @@ -15,6 +15,16 @@ * limitations under the License. */ +import { version } from '../package.json'; + export const VERTEX_TYPE = 'vertex'; export const DEFAULT_LOCATION = 'us-central1'; + +export const DEFAULT_BASE_URL = 'https://firebaseml.googleapis.com'; + +export const DEFAULT_API_VERSION = 'v2beta'; + +export const PACKAGE_VERSION = version; + +export const LANGUAGE_TAG = 'gl-js'; diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts index 35833d72691..1cae183771d 100644 --- a/packages/vertexai/src/requests/request.test.ts +++ b/packages/vertexai/src/requests/request.test.ts @@ -19,8 +19,9 @@ import { expect, use } from 'chai'; import { restore, stub } from 'sinon'; import sinonChai from 'sinon-chai'; import chaiAsPromised from 'chai-as-promised'; -import { DEFAULT_API_VERSION, RequestUrl, Task, makeRequest } from './request'; +import { RequestUrl, Task, makeRequest } from './request'; import { ApiSettings } from '../types/internal'; +import { DEFAULT_API_VERSION } from '../constants'; use(sinonChai); use(chaiAsPromised); diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts index a610ec5ba5b..54cd4a73c6b 100644 --- a/packages/vertexai/src/requests/request.ts +++ b/packages/vertexai/src/requests/request.ts @@ -18,14 +18,12 @@ import { RequestOptions } from '../types'; import { ERROR_FACTORY, VertexError } from '../errors'; import { ApiSettings } from '../types/internal'; -import { version } from '../../package.json'; - -const DEFAULT_BASE_URL = 'https://firebaseml.googleapis.com'; - -export const DEFAULT_API_VERSION = 'v2beta'; - -const PACKAGE_VERSION = version; -const PACKAGE_LOG_HEADER = 'firebase-vertexai-js'; +import { + DEFAULT_API_VERSION, + DEFAULT_BASE_URL, + LANGUAGE_TAG, + PACKAGE_VERSION +} from '../constants'; export enum Task { GENERATE_CONTENT = 'generateContent', @@ -68,10 +66,13 @@ export class RequestUrl { } /** - * Simple, but may become more complex if we add more versions to log. + * Log language and "fire/version" to x-goog-api-client */ function getClientHeaders(): string { - return `${PACKAGE_LOG_HEADER}/${PACKAGE_VERSION}`; + const loggingTags = []; + loggingTags.push(`${LANGUAGE_TAG}/`); + loggingTags.push(`fire/${PACKAGE_VERSION}`); + return loggingTags.join(' '); } export async function makeRequest( From 01a112bfea682940a32332c6ae30d223a5980c2e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 3 Apr 2024 12:24:25 -0700 Subject: [PATCH 07/28] Add appcheck header (#294) --- packages/vertexai/src/api.ts | 2 +- packages/vertexai/src/index.ts | 4 +- packages/vertexai/src/methods/count-tokens.ts | 8 +- .../src/methods/generate-content.test.ts | 39 ++++- .../vertexai/src/methods/generate-content.ts | 14 +- .../vertexai/src/models/generative-model.ts | 5 + .../vertexai/src/requests/request.test.ts | 141 +++++++++++++++--- packages/vertexai/src/requests/request.ts | 59 ++++++-- .../vertexai/src/{factory.ts => service.ts} | 8 - packages/vertexai/src/types/internal.ts | 3 + packages/vertexai/test-utils/mocks-lookup.ts | 71 ++++++--- 11 files changed, 269 insertions(+), 85 deletions(-) rename packages/vertexai/src/{factory.ts => service.ts} (87%) diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 8bd196ec7f5..02caa7ee599 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -19,7 +19,7 @@ import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Provider } from '@firebase/component'; import { getModularInstance } from '@firebase/util'; import { DEFAULT_LOCATION, VERTEX_TYPE } from './constants'; -import { VertexService } from './factory'; +import { VertexService } from './service'; import { Vertex, VertexOptions } from './public-types'; import { ERROR_FACTORY, VertexError } from './errors'; import { ModelParams, RequestOptions } from './types'; diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index ca6f3de1088..dafbd87ba82 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -22,7 +22,7 @@ */ import { registerVersion, _registerComponent } from '@firebase/app'; -import { factory } from './factory'; +import { VertexService } from './service'; import { VERTEX_TYPE } from './constants'; import { Component, ComponentType } from '@firebase/component'; import { name, version } from '../package.json'; @@ -41,7 +41,7 @@ function registerVertex(): void { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app').getImmediate(); const appCheckProvider = container.getProvider('app-check-internal'); - return factory(app, appCheckProvider, { location }); + return new VertexService(app, appCheckProvider, { location }); }, ComponentType.PUBLIC ).setMultipleInstances(true) diff --git a/packages/vertexai/src/methods/count-tokens.ts b/packages/vertexai/src/methods/count-tokens.ts index fb6808feccc..c9d43a5b6fd 100644 --- a/packages/vertexai/src/methods/count-tokens.ts +++ b/packages/vertexai/src/methods/count-tokens.ts @@ -20,7 +20,7 @@ import { CountTokensResponse, RequestOptions } from '../types'; -import { RequestUrl, Task, makeRequest } from '../requests/request'; +import { Task, makeRequest } from '../requests/request'; import { ApiSettings } from '../types/internal'; export async function countTokens( @@ -29,15 +29,11 @@ export async function countTokens( params: CountTokensRequest, requestOptions?: RequestOptions ): Promise { - const url = new RequestUrl( + const response = await makeRequest( model, Task.COUNT_TOKENS, apiSettings, false, - requestOptions - ); - const response = await makeRequest( - url, JSON.stringify(params), requestOptions ); diff --git a/packages/vertexai/src/methods/generate-content.test.ts b/packages/vertexai/src/methods/generate-content.test.ts index 57d51f586b3..4fdda9e40f8 100644 --- a/packages/vertexai/src/methods/generate-content.test.ts +++ b/packages/vertexai/src/methods/generate-content.test.ts @@ -28,6 +28,7 @@ import { HarmCategory } from '../types'; import { ApiSettings } from '../types/internal'; +import { Task } from '../requests/request'; use(sinonChai); use(chaiAsPromised); @@ -69,10 +70,14 @@ describe('generateContent()', () => { ); expect(result.response.text()).to.include('Helena'); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match((value: string) => { return value.includes('contents'); - }) + }), + undefined ); }); it('long response', async () => { @@ -88,7 +93,10 @@ describe('generateContent()', () => { expect(result.response.text()).to.include('Use Freshly Ground Coffee'); expect(result.response.text()).to.include('30 minutes of brewing'); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match.any ); }); @@ -107,7 +115,10 @@ describe('generateContent()', () => { result.response.candidates?.[0].citationMetadata?.citations.length ).to.equal(1); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match.any ); }); @@ -125,7 +136,10 @@ describe('generateContent()', () => { ); expect(result.response.text).to.throw('SAFETY'); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match.any ); }); @@ -143,7 +157,10 @@ describe('generateContent()', () => { ); expect(result.response.text).to.throw('SAFETY'); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match.any ); }); @@ -159,7 +176,10 @@ describe('generateContent()', () => { ); expect(result.response.text()).to.equal(''); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match.any ); }); @@ -175,7 +195,10 @@ describe('generateContent()', () => { ); expect(result.response.text()).to.include('30 minutes of brewing'); expect(makeRequestStub).to.be.calledWith( - match.instanceOf(request.RequestUrl), + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, match.any ); }); diff --git a/packages/vertexai/src/methods/generate-content.ts b/packages/vertexai/src/methods/generate-content.ts index 92037a1f092..2dee91f12e8 100644 --- a/packages/vertexai/src/methods/generate-content.ts +++ b/packages/vertexai/src/methods/generate-content.ts @@ -22,7 +22,7 @@ import { GenerateContentStreamResult, RequestOptions } from '../types'; -import { RequestUrl, Task, makeRequest } from '../requests/request'; +import { Task, makeRequest } from '../requests/request'; import { addHelpers } from '../requests/response-helpers'; import { processStream } from '../requests/stream-reader'; import { ApiSettings } from '../types/internal'; @@ -33,15 +33,11 @@ export async function generateContentStream( params: GenerateContentRequest, requestOptions?: RequestOptions ): Promise { - const url = new RequestUrl( + const response = await makeRequest( model, Task.STREAM_GENERATE_CONTENT, apiSettings, /* stream */ true, - requestOptions - ); - const response = await makeRequest( - url, JSON.stringify(params), requestOptions ); @@ -54,15 +50,11 @@ export async function generateContent( params: GenerateContentRequest, requestOptions?: RequestOptions ): Promise { - const url = new RequestUrl( + const response = await makeRequest( model, Task.GENERATE_CONTENT, apiSettings, /* stream */ false, - requestOptions - ); - const response = await makeRequest( - url, JSON.stringify(params), requestOptions ); diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index c9b53e08c9b..94bc87255ff 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -39,6 +39,7 @@ import { formatGenerateContentInput } from '../requests/request-helpers'; import { Vertex } from '../public-types'; import { ERROR_FACTORY, VertexError } from '../errors'; import { ApiSettings } from '../types/internal'; +import { VertexService } from '../service'; /** * Class for generative model APIs. @@ -67,6 +68,10 @@ export class GenerativeModel { project: vertex.app.options.projectId, location: vertex.location }; + if ((vertex as VertexService).appCheck) { + this._apiSettings.getAppCheckToken = () => + (vertex as VertexService).appCheck!.getToken(); + } } if (modelParams.model.includes('/')) { if (modelParams.model.startsWith('models/')) { diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts index 1cae183771d..33f77c53d17 100644 --- a/packages/vertexai/src/requests/request.test.ts +++ b/packages/vertexai/src/requests/request.test.ts @@ -19,7 +19,7 @@ import { expect, use } from 'chai'; import { restore, stub } from 'sinon'; import sinonChai from 'sinon-chai'; import chaiAsPromised from 'chai-as-promised'; -import { RequestUrl, Task, makeRequest } from './request'; +import { RequestUrl, Task, getHeaders, makeRequest } from './request'; import { ApiSettings } from '../types/internal'; import { DEFAULT_API_VERSION } from '../constants'; @@ -32,14 +32,6 @@ const fakeApiSettings: ApiSettings = { location: 'us-central1' }; -const fakeRequestUrl = new RequestUrl( - 'model-name', - Task.GENERATE_CONTENT, - fakeApiSettings, - true, - {} -); - describe('request methods', () => { afterEach(() => { restore(); @@ -116,12 +108,94 @@ describe('request methods', () => { expect(url.toString()).to.not.include('alt=sse'); }); }); + describe('getHeaders', () => { + const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'myproject', + location: 'moon', + getAppCheckToken: () => Promise.resolve({ token: 'appchecktoken' }) + }; + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + true, + {} + ); + it('adds client headers', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('x-goog-api-client')).to.include('gl-js/ fire/'); + }); + it('adds api key', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('x-goog-api-key')).to.equal('key'); + }); + it('adds app check token if it exists', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('X-Firebase-AppCheck')).to.equal('appchecktoken'); + }); + it('ignores app check token header if no appcheck service', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon' + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('X-Firebase-AppCheck')).to.be.false; + }); + it('ignores app check token header if returned token was undefined', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon', + //@ts-ignore + getAppCheckToken: () => Promise.resolve() + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('X-Firebase-AppCheck')).to.be.false; + }); + it('ignores app check token header if returned token had error', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon', + getAppCheckToken: () => + Promise.resolve({ token: 'token', error: Error('oops') }) + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('X-Firebase-AppCheck')).to.be.false; + }); + }); describe('makeRequest', () => { it('no error', async () => { const fetchStub = stub(globalThis, 'fetch').resolves({ ok: true } as Response); - const response = await makeRequest(fakeRequestUrl, ''); + const response = await makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ); expect(fetchStub).to.be.calledOnce; expect(response.ok).to.be.true; }); @@ -133,9 +207,16 @@ describe('request methods', () => { } as Response); await expect( - makeRequest(fakeRequestUrl, '', { - timeout: 0 - }) + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '', + { + timeout: 0 + } + ) ).to.be.rejectedWith('500 AbortError'); expect(fetchStub).to.be.calledOnce; }); @@ -145,9 +226,15 @@ describe('request methods', () => { status: 500, statusText: 'Server Error' } as Response); - await expect(makeRequest(fakeRequestUrl, '')).to.be.rejectedWith( - /500 Server Error/ - ); + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ) + ).to.be.rejectedWith(/500 Server Error/); expect(fetchStub).to.be.calledOnce; }); it('Network error, includes response.json()', async () => { @@ -157,9 +244,15 @@ describe('request methods', () => { statusText: 'Server Error', json: () => Promise.resolve({ error: { message: 'extra info' } }) } as Response); - await expect(makeRequest(fakeRequestUrl, '')).to.be.rejectedWith( - /500 Server Error.*extra info/ - ); + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ) + ).to.be.rejectedWith(/500 Server Error.*extra info/); expect(fetchStub).to.be.calledOnce; }); it('Network error, includes response.json() and details', async () => { @@ -181,7 +274,15 @@ describe('request methods', () => { } }) } as Response); - await expect(makeRequest(fakeRequestUrl, '')).to.be.rejectedWith( + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ) + ).to.be.rejectedWith( /500 Server Error.*extra info.*generic::invalid_argument/ ); expect(fetchStub).to.be.calledOnce; diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts index 54cd4a73c6b..84dc4403b6b 100644 --- a/packages/vertexai/src/requests/request.ts +++ b/packages/vertexai/src/requests/request.ts @@ -75,23 +75,60 @@ function getClientHeaders(): string { return loggingTags.join(' '); } +export async function getHeaders(url: RequestUrl): Promise { + 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); + } + } + return headers; +} + +export async function constructRequest( + model: string, + task: Task, + apiSettings: ApiSettings, + stream: boolean, + body: string, + requestOptions?: RequestOptions +): Promise<{ url: string; fetchOptions: RequestInit }> { + const url = new RequestUrl(model, task, apiSettings, stream, requestOptions); + return { + url: url.toString(), + fetchOptions: { + ...buildFetchOptions(requestOptions), + method: 'POST', + headers: await getHeaders(url), + body + } + }; +} + export async function makeRequest( - url: RequestUrl, + model: string, + task: Task, + apiSettings: ApiSettings, + stream: boolean, body: string, requestOptions?: RequestOptions ): Promise { + const url = new RequestUrl(model, task, apiSettings, stream, requestOptions); let response; try { - response = await fetch(url.toString(), { - ...buildFetchOptions(requestOptions), - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-goog-api-client': getClientHeaders(), - 'x-goog-api-key': url.apiSettings.apiKey - }, - body - }); + const request = await constructRequest( + model, + task, + apiSettings, + stream, + body, + requestOptions + ); + response = await fetch(request.url, request.fetchOptions); if (!response.ok) { let message = ''; try { diff --git a/packages/vertexai/src/factory.ts b/packages/vertexai/src/service.ts similarity index 87% rename from packages/vertexai/src/factory.ts rename to packages/vertexai/src/service.ts index b8aac6827ca..2f54784f5c6 100644 --- a/packages/vertexai/src/factory.ts +++ b/packages/vertexai/src/service.ts @@ -24,14 +24,6 @@ import { import { Provider } from '@firebase/component'; import { DEFAULT_LOCATION } from './constants'; -export function factory( - app: FirebaseApp, - appCheckProvider?: Provider, - options?: VertexOptions -): VertexService { - return new VertexService(app, appCheckProvider, options); -} - export class VertexService implements Vertex, _FirebaseService { appCheck: FirebaseAppCheckInternal | null; location: string; diff --git a/packages/vertexai/src/types/internal.ts b/packages/vertexai/src/types/internal.ts index 13b525c68a9..57c2e3ae160 100644 --- a/packages/vertexai/src/types/internal.ts +++ b/packages/vertexai/src/types/internal.ts @@ -15,8 +15,11 @@ * limitations under the License. */ +import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; + export interface ApiSettings { apiKey: string; project: string; location: string; + getAppCheckToken?: () => Promise; } diff --git a/packages/vertexai/test-utils/mocks-lookup.ts b/packages/vertexai/test-utils/mocks-lookup.ts index 6cd80afb252..4dd8a5963d4 100644 --- a/packages/vertexai/test-utils/mocks-lookup.ts +++ b/packages/vertexai/test-utils/mocks-lookup.ts @@ -1,22 +1,57 @@ +/** + * @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. + */ + // Generated from mocks text files. export const mocksLookup: Record = { - "streaming-failure-empty-content.txt": "data: {\"candidates\": [{\"content\": {},\"index\": 0}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", - "streaming-failure-finish-reason-safety.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"No\"}]},\"finishReason\": \"SAFETY\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"HIGH\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", - "streaming-failure-prompt-blocked-safety.txt": "data: {\"promptFeedback\": {\"blockReason\": \"SAFETY\",\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"HIGH\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", - "streaming-failure-recitation-no-content.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"Copyrighted text goes here\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"More copyrighted text\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"LOW\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 30,\"endIndex\": 179,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\ndata: {\"candidates\": [{\"finishReason\": \"RECITATION\",\"index\": 0}]}\r\n\r\n", - "streaming-success-basic-reply-long.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on.\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\n", - "streaming-success-basic-reply-short.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"Cheyenne\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", - "streaming-success-citations.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \", and other quantities are quantized, meaning they can exist only in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \\\"collapses\\\" into a single state.\\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \\n\\n3. **Applications:**\\n - **Quantum Computing:** It explores the use of quantum\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 574,\"endIndex\": 705,\"uri\": \"https://www.example.com\",\"license\": \"\"},{\"startIndex\": 899,\"endIndex\": 1026,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \\n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \\n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \\n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\\"Quantum Mechanics for Mathematicians\\\" by James Glimm and Arthur Jaffe\\n - \\\"Principles of Quantum Mechanics\\\" by R. Shankar\\n - \\\"Quantum Mechanics: Concepts and Applications\\\" by Nouredine Zettili\\n - **Online Courses:**\\n - \\\"Quantum Mechanics I\\\" by MIT OpenCourseWare\\n - \\\"Quantum Mechanics for Everyone\\\" by Coursera\\n - \\\"Quantum Mechanics\\\" by Stanford Online\\n - **Documentaries and Videos:**\\n - \\\"Quantum Mechanics: The Strangest Theory\\\" (BBC Documentary)\\n - \\\"Quantum Mechanics Explained Simply\\\" by Veritasium (YouTube Channel)\\n - \\\"What is Quantum Mechanics?\\\" by Minute\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 574,\"endIndex\": 705,\"uri\": \"https://www.example.com\",\"license\": \"\"},{\"startIndex\": 899,\"endIndex\": 1026,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"Physics (YouTube Channel)\"}],\"role\": \"model\"},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}],\"citationMetadata\": {\"citations\": [{\"startIndex\": 574,\"endIndex\": 705,\"uri\": \"https://www.example.com\",\"license\": \"\"},{\"startIndex\": 899,\"endIndex\": 1026,\"uri\": \"https://www.example.com\",\"license\": \"\"}]}}]}\r\n\r\n", - "streaming-success-function-call-short.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{ \"functionCall\": { \"name\": \"getTemperature\", \"args\": { \"city\": \"San Jose\" } } }]}, \"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\n", - "streaming-success-utf8.txt": "data: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \"秋风瑟瑟,叶落纷纷,\\n西风残照,寒\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ], \"promptFeedback\": { \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } }\r\n\r\ndata: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \"霜渐浓。\\n枫叶红了,菊花黄了,\\n秋雨绵绵,秋意浓浓。\\n\\n秋夜漫漫,思\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ] }\r\n\r\ndata: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \"绪万千,\\n明月当空,星星眨眼。\\n思念远方的亲人,\\n祝愿他们幸福安康。\\n\\n秋天是收获的季节,\\n人们忙着收割庄稼,\\n为一年的辛劳画上圆满的句号。\\n秋天也是团圆的季节\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ] }\r\n\r\ndata: { \"candidates\": [ { \"content\": { \"parts\": [ { \"text\": \",\\n一家人围坐在一起,\\n分享丰收的喜悦,共度美好时光。\\n\\n秋天是一个美丽的季节,\\n它有着独特的韵味,\\n让人沉醉其中,流连忘返。\\n让我们一起欣赏秋天的美景,\\n感受秋天的气息,领悟秋天的哲理。\" } ], \"role\": \"model\" }, \"finishReason\": \"STOP\", \"index\": 0, \"safetyRatings\": [ { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HATE_SPEECH\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_HARASSMENT\", \"probability\": \"NEGLIGIBLE\" }, { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"probability\": \"NEGLIGIBLE\" } ] } ] }\r\n\r\n", - "streaming-unknown-enum.txt": "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}],\"promptFeedback\": {\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and\"}]},\"finishReason\": \"STOP\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\"probability\": \"NEGLIGIBLE\"}]}]}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on.\"}]},\"finishReason\": \"FAKE_ENUM\",\"index\": 0,\"safetyRatings\": [{\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HATE_SPEECH\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_HARASSMENT\",\"probability\": \"NEGLIGIBLE\"},{\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM\",\"probability\": \"NEGLIGIBLE_UNKNOWN_ENUM\"}]}]}\r\n\r\n", - "unary-failure-empty-content.json": "{\n \"candidates\": [\n {\n \"content\": {},\n \"index\": 0\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", - "unary-failure-finish-reason-safety.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"No\"\n }\n ]\n },\n \"finishReason\": \"SAFETY\",\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"HIGH\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", - "unary-failure-image-rejected.json": "{\n \"error\": {\n \"code\": 400,\n \"message\": \"Request contains an invalid argument.\",\n \"status\": \"INVALID_ARGUMENT\",\n \"details\": [\n {\n \"@type\": \"type.googleapis.com/google.rpc.DebugInfo\",\n \"detail\": \"[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short\"\n }\n ]\n }\n}\n", - "unary-failure-prompt-blocked-safety.json": "{\n \"promptFeedback\": {\n \"blockReason\": \"SAFETY\",\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"HIGH\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", - "unary-success-basic-reply-long.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing.\"\n }\n ]\n },\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", - "unary-success-basic-reply-short.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"Helena\"\n }\n ]\n },\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", - "unary-success-citations.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum, and other quantities are quantized, meaning they can only exist in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Quantum Superposition:** A quantum system can exist in multiple states simultaneously until it is measured. \\n - **Quantum Entanglement:** Two or more particles can become linked in such a way that the state of one affects the state of the others, regardless of the distance between them. \\n\\n3. **Implications and Applications:**\\n - **Atomic and Molecular Structure:** Quantum mechanics explains the structure of atoms, molecules, and chemical bonds. \\n - **Quantum Computing:** It enables the development of quantum computers that can solve certain computational problems much faster than classical computers. \\n - **Quantum Cryptography:** Quantum principles are used to develop secure communication methods. \\n - **Quantum Field Theory (QFT):** A relativistic theory that describes interactions between particles in quantum mechanics. It underpins the Standard Model of Physics. \\n - **Quantum Gravity:** Attempts to reconcile quantum mechanics with general relativity to explain the behavior of matter and gravity at very small scales. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\\"Quantum Mechanics for Mathematicians\\\" by James Glimm and Arthur Jaffe \\n - \\\"Principles of Quantum Mechanics\\\" by R. Shankar\\n - \\\"Quantum Mechanics: Concepts and Applications\\\" by Nouredine Zettili \\n - **Online Courses and Tutorials:**\\n - [Quantum Mechanics I](https://www.example.com) on Coursera\\n - [MIT OpenCourseWare](https://www.example.com) Quantum Physics I course materials \\n - [Khan Academy](https://www.example.com) Quantum Physics video tutorials \\n - **Videos and Documentaries:**\\n - [Quantum Mechanics - Crash Course Physics](https://www.example.com)\\n - [Quantum Mechanics: The Strangest Theory](https://www.example.com)\\n - [BBC: The Quantum World with Jim Al-Khalili](https://www.example.com)\\n - [NOVA: The Fabric of the Cosmos](https://www.example.com)\"\n }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": \"STOP\",\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ],\n \"citationMetadata\": {\n \"citations\": [\n {\n \"startIndex\": 574,\n \"endIndex\": 705,\n \"uri\": \"https://www.example.com\",\n \"license\": \"\"\n }\n ]\n }\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n}\n", - "unary-unknown-enum.json": "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": [\n {\n \"text\": \"1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing.\"\n }\n ]\n },\n \"index\": 0,\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT_ENUM_NEW\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",\n \"probability\": \"NEGLIGIBLE\"\n }\n ]\n }\n ],\n \"promptFeedback\": {\n \"safetyRatings\": [\n {\n \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HATE_SPEECH\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_HARASSMENT\",\n \"probability\": \"NEGLIGIBLE\"\n },\n {\n \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT_LIKE_A_NEW_ENUM\",\n \"probability\": \"NEGLIGIBLE_NEW_ENUM\"\n }\n ]\n }\n}\n" + 'streaming-failure-empty-content.txt': + 'data: {"candidates": [{"content": {},"index": 0}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', + 'streaming-failure-finish-reason-safety.txt': + 'data: {"candidates": [{"content": {"parts": [{"text": "No"}]},"finishReason": "SAFETY","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', + 'streaming-failure-prompt-blocked-safety.txt': + 'data: {"promptFeedback": {"blockReason": "SAFETY","safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', + 'streaming-failure-recitation-no-content.txt': + 'data: {"candidates": [{"content": {"parts": [{"text": "Copyrighted text goes here"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\ndata: {"candidates": [{"finishReason": "RECITATION","index": 0}]}\r\n\r\n', + 'streaming-success-basic-reply-long.txt': + 'data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat\'s health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog\'s health and detecting any potential health issues early on."}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\n', + 'streaming-success-basic-reply-short.txt': + 'data: {"candidates": [{"content": {"parts": [{"text": "Cheyenne"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', + 'streaming-success-citations.txt': + 'data: {"candidates": [{"content": {"parts": [{"text": "1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": ", and other quantities are quantized, meaning they can exist only in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \\"collapses\\" into a single state.\\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \\n\\n3. **Applications:**\\n - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \\n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \\n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \\n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\"Quantum Mechanics for Mathematicians\\" by James Glimm and Arthur Jaffe\\n - \\"Principles of Quantum Mechanics\\" by R. Shankar\\n - \\"Quantum Mechanics: Concepts and Applications\\" by Nouredine Zettili\\n - **Online Courses:**\\n - \\"Quantum Mechanics I\\" by MIT OpenCourseWare\\n - \\"Quantum Mechanics for Everyone\\" by Coursera\\n - \\"Quantum Mechanics\\" by Stanford Online\\n - **Documentaries and Videos:**\\n - \\"Quantum Mechanics: The Strangest Theory\\" (BBC Documentary)\\n - \\"Quantum Mechanics Explained Simply\\" by Veritasium (YouTube Channel)\\n - \\"What is Quantum Mechanics?\\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\n', + 'streaming-success-function-call-short.txt': + 'data: {"candidates": [{"content": {"parts": [{ "functionCall": { "name": "getTemperature", "args": { "city": "San Jose" } } }]}, "finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', + 'streaming-success-utf8.txt': + 'data: { "candidates": [ { "content": { "parts": [ { "text": "秋风瑟瑟,叶落纷纷,\\n西风残照,寒" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ], "promptFeedback": { "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } }\r\n\r\ndata: { "candidates": [ { "content": { "parts": [ { "text": "霜渐浓。\\n枫叶红了,菊花黄了,\\n秋雨绵绵,秋意浓浓。\\n\\n秋夜漫漫,思" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] }\r\n\r\ndata: { "candidates": [ { "content": { "parts": [ { "text": "绪万千,\\n明月当空,星星眨眼。\\n思念远方的亲人,\\n祝愿他们幸福安康。\\n\\n秋天是收获的季节,\\n人们忙着收割庄稼,\\n为一年的辛劳画上圆满的句号。\\n秋天也是团圆的季节" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] }\r\n\r\ndata: { "candidates": [ { "content": { "parts": [ { "text": ",\\n一家人围坐在一起,\\n分享丰收的喜悦,共度美好时光。\\n\\n秋天是一个美丽的季节,\\n它有着独特的韵味,\\n让人沉醉其中,流连忘返。\\n让我们一起欣赏秋天的美景,\\n感受秋天的气息,领悟秋天的哲理。" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] }\r\n\r\n', + 'streaming-unknown-enum.txt': + 'data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat\'s health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog\'s health and detecting any potential health issues early on."}]},"finishReason": "FAKE_ENUM","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM","probability": "NEGLIGIBLE_UNKNOWN_ENUM"}]}]}\r\n\r\n', + 'unary-failure-empty-content.json': + '{\n "candidates": [\n {\n "content": {},\n "index": 0\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', + 'unary-failure-finish-reason-safety.json': + '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "No"\n }\n ]\n },\n "finishReason": "SAFETY",\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "HIGH"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', + 'unary-failure-image-rejected.json': + '{\n "error": {\n "code": 400,\n "message": "Request contains an invalid argument.",\n "status": "INVALID_ARGUMENT",\n "details": [\n {\n "@type": "type.googleapis.com/google.rpc.DebugInfo",\n "detail": "[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short"\n }\n ]\n }\n}\n', + 'unary-failure-prompt-blocked-safety.json': + '{\n "promptFeedback": {\n "blockReason": "SAFETY",\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "HIGH"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', + 'unary-success-basic-reply-long.json': + '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won\'t extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing."\n }\n ]\n },\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', + 'unary-success-basic-reply-short.json': + '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "Helena"\n }\n ]\n },\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', + 'unary-success-citations.json': + '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum, and other quantities are quantized, meaning they can only exist in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Quantum Superposition:** A quantum system can exist in multiple states simultaneously until it is measured. \\n - **Quantum Entanglement:** Two or more particles can become linked in such a way that the state of one affects the state of the others, regardless of the distance between them. \\n\\n3. **Implications and Applications:**\\n - **Atomic and Molecular Structure:** Quantum mechanics explains the structure of atoms, molecules, and chemical bonds. \\n - **Quantum Computing:** It enables the development of quantum computers that can solve certain computational problems much faster than classical computers. \\n - **Quantum Cryptography:** Quantum principles are used to develop secure communication methods. \\n - **Quantum Field Theory (QFT):** A relativistic theory that describes interactions between particles in quantum mechanics. It underpins the Standard Model of Physics. \\n - **Quantum Gravity:** Attempts to reconcile quantum mechanics with general relativity to explain the behavior of matter and gravity at very small scales. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\"Quantum Mechanics for Mathematicians\\" by James Glimm and Arthur Jaffe \\n - \\"Principles of Quantum Mechanics\\" by R. Shankar\\n - \\"Quantum Mechanics: Concepts and Applications\\" by Nouredine Zettili \\n - **Online Courses and Tutorials:**\\n - [Quantum Mechanics I](https://www.example.com) on Coursera\\n - [MIT OpenCourseWare](https://www.example.com) Quantum Physics I course materials \\n - [Khan Academy](https://www.example.com) Quantum Physics video tutorials \\n - **Videos and Documentaries:**\\n - [Quantum Mechanics - Crash Course Physics](https://www.example.com)\\n - [Quantum Mechanics: The Strangest Theory](https://www.example.com)\\n - [BBC: The Quantum World with Jim Al-Khalili](https://www.example.com)\\n - [NOVA: The Fabric of the Cosmos](https://www.example.com)"\n }\n ],\n "role": "model"\n },\n "finishReason": "STOP",\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ],\n "citationMetadata": {\n "citations": [\n {\n "startIndex": 574,\n "endIndex": 705,\n "uri": "https://www.example.com",\n "license": ""\n }\n ]\n }\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', + 'unary-unknown-enum.json': + '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won\'t extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing."\n }\n ]\n },\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT_ENUM_NEW",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT_LIKE_A_NEW_ENUM",\n "probability": "NEGLIGIBLE_NEW_ENUM"\n }\n ]\n }\n}\n' }; From e946e73caa7997162df8a92918bf522711d84421 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 3 Apr 2024 12:30:22 -0700 Subject: [PATCH 08/28] update firebase dep versions --- packages/vertexai/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index 8d18c7bbdd4..942db5ca42f 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -44,15 +44,15 @@ "@firebase/app-types": "0.x" }, "dependencies": { - "@firebase/app-check-interop-types": "0.3.0", - "@firebase/component": "0.6.5", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.4", + "@firebase/app-check-interop-types": "0.3.1", + "@firebase/component": "0.6.6", + "@firebase/logger": "0.4.1", + "@firebase/util": "1.9.5", "tslib": "^2.1.0" }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app": "0.9.29", + "@firebase/app": "0.10.0", "@rollup/plugin-json": "4.1.0", "rollup": "2.79.1", "rollup-plugin-replace": "2.2.0", From d27cd8e6f463201193cd53af2445d53443629eed Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 4 Apr 2024 12:25:50 -0700 Subject: [PATCH 09/28] Enable first npm publish (#8120) --- .github/workflows/canary-deploy.yml | 1 + .../workflows/prerelease-manual-deploy.yml | 1 + .github/workflows/release-prod.yml | 1 + .github/workflows/release-staging.yml | 1 + packages/vertexai/src/api.test.ts | 16 +++++++-------- packages/vertexai/src/api.ts | 16 +++++++-------- packages/vertexai/src/index.ts | 4 ++-- .../src/models/generative-model.test.ts | 12 +++++------ .../vertexai/src/models/generative-model.ts | 20 +++++++++---------- packages/vertexai/src/public-types.ts | 8 ++++---- packages/vertexai/src/service.ts | 6 +++--- scripts/release/utils/publish.ts | 10 +++++++--- 12 files changed, 52 insertions(+), 44 deletions(-) diff --git a/.github/workflows/canary-deploy.yml b/.github/workflows/canary-deploy.yml index 73ce0044c1a..9c4d7de448b 100644 --- a/.github/workflows/canary-deploy.yml +++ b/.github/workflows/canary-deploy.yml @@ -73,6 +73,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/prerelease-manual-deploy.yml b/.github/workflows/prerelease-manual-deploy.yml index 54f75383f95..13168c0cee7 100644 --- a/.github/workflows/prerelease-manual-deploy.yml +++ b/.github/workflows/prerelease-manual-deploy.yml @@ -76,6 +76,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index 09d1797ec4c..12f5766d66e 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -86,6 +86,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index 6a687ed5ee2..057f0944dd3 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -112,6 +112,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/packages/vertexai/src/api.test.ts b/packages/vertexai/src/api.test.ts index efc2705921a..5c25cce7ef9 100644 --- a/packages/vertexai/src/api.test.ts +++ b/packages/vertexai/src/api.test.ts @@ -17,11 +17,11 @@ import { ModelParams } from './types'; import { getGenerativeModel } from './api'; import { expect } from 'chai'; -import { Vertex } from './public-types'; +import { VertexAI } from './public-types'; import { GenerativeModel } from './models/generative-model'; import { VertexError } from './errors'; -const fakeVertex: Vertex = { +const fakeVertexAI: VertexAI = { app: { name: 'DEFAULT', automaticDataCollectionEnabled: true, @@ -35,30 +35,30 @@ const fakeVertex: Vertex = { describe('Top level API', () => { it('getGenerativeModel throws if no model is provided', () => { - expect(() => getGenerativeModel(fakeVertex, {} as ModelParams)).to.throw( + expect(() => getGenerativeModel(fakeVertexAI, {} as ModelParams)).to.throw( VertexError.NO_MODEL ); }); it('getGenerativeModel throws if no apiKey is provided', () => { const fakeVertexNoApiKey = { - ...fakeVertex, + ...fakeVertexAI, app: { options: { projectId: 'my-project' } } - } as Vertex; + } as VertexAI; expect(() => getGenerativeModel(fakeVertexNoApiKey, { model: 'my-model' }) ).to.throw(VertexError.NO_API_KEY); }); it('getGenerativeModel throws if no projectId is provided', () => { const fakeVertexNoProject = { - ...fakeVertex, + ...fakeVertexAI, app: { options: { apiKey: 'my-key' } } - } as Vertex; + } as VertexAI; expect(() => getGenerativeModel(fakeVertexNoProject, { model: 'my-model' }) ).to.throw(VertexError.NO_PROJECT_ID); }); it('getGenerativeModel gets a GenerativeModel', () => { - const genModel = getGenerativeModel(fakeVertex, { model: 'my-model' }); + const genModel = getGenerativeModel(fakeVertexAI, { model: 'my-model' }); expect(genModel).to.be.an.instanceOf(GenerativeModel); expect(genModel.model).to.equal('publishers/google/models/my-model'); }); diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 02caa7ee599..3702a5ff17f 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -19,8 +19,8 @@ import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Provider } from '@firebase/component'; import { getModularInstance } from '@firebase/util'; import { DEFAULT_LOCATION, VERTEX_TYPE } from './constants'; -import { VertexService } from './service'; -import { Vertex, VertexOptions } from './public-types'; +import { VertexAIService } from './service'; +import { VertexAI, VertexAIOptions } from './public-types'; import { ERROR_FACTORY, VertexError } from './errors'; import { ModelParams, RequestOptions } from './types'; import { GenerativeModel } from './models/generative-model'; @@ -31,21 +31,21 @@ export { GenerativeModel }; declare module '@firebase/component' { interface NameServiceMapping { - [VERTEX_TYPE]: VertexService; + [VERTEX_TYPE]: VertexAIService; } } /** - * Returns an {@link Vertex} instance for the given app. + * Returns an {@link VertexAI} instance for the given app. * * @public * * @param app - The {@link @firebase/app#FirebaseApp} to use. */ -export function getVertex( +export function getVertexAI( app: FirebaseApp = getApp(), - options?: VertexOptions -): Vertex { + options?: VertexAIOptions +): VertexAI { app = getModularInstance(app); // Dependencies const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); @@ -56,7 +56,7 @@ export function getVertex( } export function getGenerativeModel( - vertex: Vertex, + vertex: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions ): GenerativeModel { diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index dafbd87ba82..9a0c717b9ee 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -22,7 +22,7 @@ */ import { registerVersion, _registerComponent } from '@firebase/app'; -import { VertexService } from './service'; +import { VertexAIService } from './service'; import { VERTEX_TYPE } from './constants'; import { Component, ComponentType } from '@firebase/component'; import { name, version } from '../package.json'; @@ -41,7 +41,7 @@ function registerVertex(): void { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app').getImmediate(); const appCheckProvider = container.getProvider('app-check-internal'); - return new VertexService(app, appCheckProvider, { location }); + return new VertexAIService(app, appCheckProvider, { location }); }, ComponentType.PUBLIC ).setMultipleInstances(true) diff --git a/packages/vertexai/src/models/generative-model.test.ts b/packages/vertexai/src/models/generative-model.test.ts index f3623960872..5c930f36fc7 100644 --- a/packages/vertexai/src/models/generative-model.test.ts +++ b/packages/vertexai/src/models/generative-model.test.ts @@ -16,9 +16,9 @@ */ import { expect } from 'chai'; import { GenerativeModel } from './generative-model'; -import { Vertex } from '../public-types'; +import { VertexAI } from '../public-types'; -const fakeVertex: Vertex = { +const fakeVertexAI: VertexAI = { app: { name: 'DEFAULT', automaticDataCollectionEnabled: true, @@ -32,23 +32,23 @@ const fakeVertex: Vertex = { describe('GenerativeModel', () => { it('handles plain model name', () => { - const genModel = new GenerativeModel(fakeVertex, { model: 'my-model' }); + const genModel = new GenerativeModel(fakeVertexAI, { model: 'my-model' }); expect(genModel.model).to.equal('publishers/google/models/my-model'); }); it('handles models/ prefixed model name', () => { - const genModel = new GenerativeModel(fakeVertex, { + const genModel = new GenerativeModel(fakeVertexAI, { model: 'models/my-model' }); expect(genModel.model).to.equal('publishers/google/models/my-model'); }); it('handles full model name', () => { - const genModel = new GenerativeModel(fakeVertex, { + const genModel = new GenerativeModel(fakeVertexAI, { model: 'publishers/google/models/my-model' }); expect(genModel.model).to.equal('publishers/google/models/my-model'); }); it('handles prefixed tuned model name', () => { - const genModel = new GenerativeModel(fakeVertex, { + const genModel = new GenerativeModel(fakeVertexAI, { model: 'tunedModels/my-model' }); expect(genModel.model).to.equal('tunedModels/my-model'); diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index 94bc87255ff..04f30036365 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -36,10 +36,10 @@ import { import { ChatSession } from '../methods/chat-session'; import { countTokens } from '../methods/count-tokens'; import { formatGenerateContentInput } from '../requests/request-helpers'; -import { Vertex } from '../public-types'; +import { VertexAI } from '../public-types'; import { ERROR_FACTORY, VertexError } from '../errors'; import { ApiSettings } from '../types/internal'; -import { VertexService } from '../service'; +import { VertexAIService } from '../service'; /** * Class for generative model APIs. @@ -54,23 +54,23 @@ export class GenerativeModel { tools?: Tool[]; constructor( - vertex: Vertex, + vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions ) { - if (!vertex.app?.options?.apiKey) { + if (!vertexAI.app?.options?.apiKey) { throw ERROR_FACTORY.create(VertexError.NO_API_KEY); - } else if (!vertex.app?.options?.projectId) { + } else if (!vertexAI.app?.options?.projectId) { throw ERROR_FACTORY.create(VertexError.NO_PROJECT_ID); } else { this._apiSettings = { - apiKey: vertex.app.options.apiKey, - project: vertex.app.options.projectId, - location: vertex.location + apiKey: vertexAI.app.options.apiKey, + project: vertexAI.app.options.projectId, + location: vertexAI.location }; - if ((vertex as VertexService).appCheck) { + if ((vertexAI as VertexAIService).appCheck) { this._apiSettings.getAppCheckToken = () => - (vertex as VertexService).appCheck!.getToken(); + (vertexAI as VertexAIService).appCheck!.getToken(); } } if (modelParams.model.includes('/')) { diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index 8724759e00a..73d9fb6d376 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -20,17 +20,17 @@ import { FirebaseApp } from '@firebase/app'; export * from './types'; /** - * An instance of Firebase Vertex. + * An instance of Firebase Vertex AI. * @public */ -export interface Vertex { +export interface VertexAI { /** - * The {@link @firebase/app#FirebaseApp} this {@link Vertex} instance is associated with. + * The {@link @firebase/app#FirebaseApp} this {@link VertexAI} instance is associated with. */ app: FirebaseApp; location: string; } -export interface VertexOptions { +export interface VertexAIOptions { location?: string; } diff --git a/packages/vertexai/src/service.ts b/packages/vertexai/src/service.ts index 2f54784f5c6..a061fc4ad65 100644 --- a/packages/vertexai/src/service.ts +++ b/packages/vertexai/src/service.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp, _FirebaseService } from '@firebase/app'; -import { Vertex, VertexOptions } from './public-types'; +import { VertexAI, VertexAIOptions } from './public-types'; import { AppCheckInternalComponentName, FirebaseAppCheckInternal @@ -24,14 +24,14 @@ import { import { Provider } from '@firebase/component'; import { DEFAULT_LOCATION } from './constants'; -export class VertexService implements Vertex, _FirebaseService { +export class VertexAIService implements VertexAI, _FirebaseService { appCheck: FirebaseAppCheckInternal | null; location: string; constructor( public app: FirebaseApp, appCheckProvider?: Provider, - public options?: VertexOptions + public options?: VertexAIOptions ) { const appCheck = appCheckProvider?.getImmediate({ optional: true }); this.appCheck = appCheck || null; diff --git a/scripts/release/utils/publish.ts b/scripts/release/utils/publish.ts index 55d641cc510..c3f40f0d888 100644 --- a/scripts/release/utils/publish.ts +++ b/scripts/release/utils/publish.ts @@ -75,9 +75,13 @@ export async function publishInCI( continue; } } catch (e) { - // 404 from NPM indicates the package doesn't exist there. - console.log(`Skipping pkg: ${pkg} - it has never been published to NPM.`); - continue; + if (version !== '0.0.1') { + // 404 from NPM indicates the package doesn't exist there. + console.log( + `Skipping pkg: ${pkg} - it has never been published to NPM.` + ); + continue; + } } const tag = `${pkg}@${version}`; From bf7d2d883769f2c525d00cb836b955dfbdc99e21 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 4 Apr 2024 12:45:21 -0700 Subject: [PATCH 10/28] Remove apiVersion and location options (#8124) --- packages/vertexai/src/api.ts | 9 +++---- packages/vertexai/src/index.test.ts | 27 ------------------- packages/vertexai/src/index.ts | 4 +-- packages/vertexai/src/public-types.ts | 4 --- .../vertexai/src/requests/request.test.ts | 12 --------- packages/vertexai/src/requests/request.ts | 3 ++- packages/vertexai/src/service.ts | 8 +++--- packages/vertexai/src/types/requests.ts | 5 ---- 8 files changed, 11 insertions(+), 61 deletions(-) delete mode 100644 packages/vertexai/src/index.test.ts diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 3702a5ff17f..19d250ccb79 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -20,7 +20,7 @@ import { Provider } from '@firebase/component'; import { getModularInstance } from '@firebase/util'; import { DEFAULT_LOCATION, VERTEX_TYPE } from './constants'; import { VertexAIService } from './service'; -import { VertexAI, VertexAIOptions } from './public-types'; +import { VertexAI } from './public-types'; import { ERROR_FACTORY, VertexError } from './errors'; import { ModelParams, RequestOptions } from './types'; import { GenerativeModel } from './models/generative-model'; @@ -42,16 +42,13 @@ declare module '@firebase/component' { * * @param app - The {@link @firebase/app#FirebaseApp} to use. */ -export function getVertexAI( - app: FirebaseApp = getApp(), - options?: VertexAIOptions -): VertexAI { +export function getVertexAI(app: FirebaseApp = getApp()): VertexAI { app = getModularInstance(app); // Dependencies const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); return vertexProvider.getImmediate({ - identifier: options?.location || DEFAULT_LOCATION + identifier: DEFAULT_LOCATION }); } diff --git a/packages/vertexai/src/index.test.ts b/packages/vertexai/src/index.test.ts deleted file mode 100644 index 95a9329f2d0..00000000000 --- a/packages/vertexai/src/index.test.ts +++ /dev/null @@ -1,27 +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. - */ - -import { expect } from 'chai'; - -describe('Simple test', () => { - it('Should skip this test'); - it('Should test this async thing', async () => { - // Do some async assertions, you can use `await` syntax if it helps - const val = await Promise.resolve(42); - expect(val).to.equal(42); - }); -}); diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index 9a0c717b9ee..403d690a370 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -37,11 +37,11 @@ function registerVertex(): void { _registerComponent( new Component( VERTEX_TYPE, - (container, { instanceIdentifier: location }) => { + container => { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app').getImmediate(); const appCheckProvider = container.getProvider('app-check-internal'); - return new VertexAIService(app, appCheckProvider, { location }); + return new VertexAIService(app, appCheckProvider); }, ComponentType.PUBLIC ).setMultipleInstances(true) diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index 73d9fb6d376..a662580da86 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -30,7 +30,3 @@ export interface VertexAI { app: FirebaseApp; location: string; } - -export interface VertexAIOptions { - location?: string; -} diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts index 33f77c53d17..0cd2d2686ce 100644 --- a/packages/vertexai/src/requests/request.test.ts +++ b/packages/vertexai/src/requests/request.test.ts @@ -71,18 +71,6 @@ describe('request methods', () => { ); expect(url.toString()).to.include(DEFAULT_API_VERSION); }); - it('custom apiVersion', async () => { - const url = new RequestUrl( - 'models/model-name', - Task.GENERATE_CONTENT, - fakeApiSettings, - false, - { apiVersion: 'v100omega' } - ); - expect(url.toString()).to.include( - '/v100omega/projects/my-project/locations/us-central1/models/model-name' - ); - }); it('custom baseUrl', async () => { const url = new RequestUrl( 'models/model-name', diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts index 84dc4403b6b..58a22b4ed33 100644 --- a/packages/vertexai/src/requests/request.ts +++ b/packages/vertexai/src/requests/request.ts @@ -40,7 +40,8 @@ export class RequestUrl { public requestOptions?: RequestOptions ) {} toString(): string { - const apiVersion = this.requestOptions?.apiVersion || DEFAULT_API_VERSION; + // TODO: allow user-set option if that feature becomes available + const apiVersion = DEFAULT_API_VERSION; const baseUrl = this.requestOptions?.baseUrl || DEFAULT_BASE_URL; let url = `${baseUrl}/${apiVersion}`; url += `/projects/${this.apiSettings.project}`; diff --git a/packages/vertexai/src/service.ts b/packages/vertexai/src/service.ts index a061fc4ad65..6925deb2a8c 100644 --- a/packages/vertexai/src/service.ts +++ b/packages/vertexai/src/service.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp, _FirebaseService } from '@firebase/app'; -import { VertexAI, VertexAIOptions } from './public-types'; +import { VertexAI } from './public-types'; import { AppCheckInternalComponentName, FirebaseAppCheckInternal @@ -30,12 +30,12 @@ export class VertexAIService implements VertexAI, _FirebaseService { constructor( public app: FirebaseApp, - appCheckProvider?: Provider, - public options?: VertexAIOptions + appCheckProvider?: Provider ) { const appCheck = appCheckProvider?.getImmediate({ optional: true }); this.appCheck = appCheck || null; - this.location = this.options?.location || DEFAULT_LOCATION; + // TODO: add in user-set location option when that feature is available + this.location = DEFAULT_LOCATION; } _delete(): Promise { diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index a1092903f0f..0ab2f14b8c1 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -93,11 +93,6 @@ export interface RequestOptions { * Request timeout in milliseconds. */ timeout?: number; - /** - * Version of API endpoint to call (e.g. "v1" or "v1beta"). If not specified, - * defaults to latest stable version. - */ - apiVersion?: string; /** * Base url for endpoint. Defaults to https://firebaseml.googleapis.com */ From 8b70fc586781a736df42270059df8f9c2745b146 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 4 Apr 2024 14:07:46 -0700 Subject: [PATCH 11/28] Set package to public (#8125) --- packages/vertexai/package.json | 1 - scripts/release/utils/publish.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index 942db5ca42f..d80d4d5e010 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -1,7 +1,6 @@ { "name": "@firebase/vertexai", "version": "0.0.1", - "private": true, "description": "A template package for new firebase packages", "author": "Firebase (https://firebase.google.com/)", "engines": { diff --git a/scripts/release/utils/publish.ts b/scripts/release/utils/publish.ts index c3f40f0d888..1ed3e6261d7 100644 --- a/scripts/release/utils/publish.ts +++ b/scripts/release/utils/publish.ts @@ -75,7 +75,8 @@ export async function publishInCI( continue; } } catch (e) { - if (version !== '0.0.1') { + const versionParts = version.split('-'); + if (versionParts[0] !== '0.0.1') { // 404 from NPM indicates the package doesn't exist there. console.log( `Skipping pkg: ${pkg} - it has never been published to NPM.` From acef5e31a80478a797a05101b0abc0f32f18ba4a Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 5 Apr 2024 10:37:10 -0700 Subject: [PATCH 12/28] Final fixes blocking publish (#8126) --- .github/workflows/canary-deploy.yml | 2 +- .github/workflows/prerelease-manual-deploy.yml | 2 +- .github/workflows/release-prod.yml | 2 +- .github/workflows/release-staging.yml | 2 +- packages/vertexai/package.json | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/canary-deploy.yml b/.github/workflows/canary-deploy.yml index 9c4d7de448b..8b359aaccdf 100644 --- a/.github/workflows/canary-deploy.yml +++ b/.github/workflows/canary-deploy.yml @@ -73,7 +73,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} - NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/prerelease-manual-deploy.yml b/.github/workflows/prerelease-manual-deploy.yml index 13168c0cee7..c60302c1eac 100644 --- a/.github/workflows/prerelease-manual-deploy.yml +++ b/.github/workflows/prerelease-manual-deploy.yml @@ -76,7 +76,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} - NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index 12f5766d66e..ed524abd4ad 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -86,7 +86,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} - NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index 057f0944dd3..699113effa6 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -112,7 +112,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} - NPM_TOKEN_VERTEX: ${{secrets.NPM_TOKEN_VERTEX}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index d80d4d5e010..98f31fc820c 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -61,7 +61,7 @@ "repository": { "directory": "packages/vertexai", "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" + "url": "git+https://github.com/firebase/firebase-js-sdk.git" }, "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" @@ -73,4 +73,4 @@ ], "reportDir": "./coverage/node" } -} \ No newline at end of file +} From f25ccbbafcf7753c967f326f8b2387a82aa82b10 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 8 Apr 2024 13:49:54 -0700 Subject: [PATCH 13/28] Add additional Vertex types (#8131) --- packages/vertexai/src/types/enums.ts | 29 +++++++++++ packages/vertexai/src/types/requests.ts | 5 +- packages/vertexai/src/types/responses.ts | 61 +++++++++++++++++++++++- 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/src/types/enums.ts b/packages/vertexai/src/types/enums.ts index cdb8e4bca79..817450a069b 100644 --- a/packages/vertexai/src/types/enums.ts +++ b/packages/vertexai/src/types/enums.ts @@ -56,6 +56,18 @@ export enum HarmBlockThreshold { BLOCK_NONE = 'BLOCK_NONE' } +/** + * @public + */ +export enum HarmBlockMethod { + // The harm block method is unspecified. + HARM_BLOCK_METHOD_UNSPECIFIED = 'HARM_BLOCK_METHOD_UNSPECIFIED', + // The harm block method uses both probability and severity scores. + SEVERITY = 'SEVERITY', + // The harm block method uses the probability score. + PROBABILITY = 'PROBABILITY' +} + /** * Probability that a prompt or candidate matches a harm category. * @public @@ -73,6 +85,23 @@ export enum HarmProbability { HIGH = 'HIGH' } +/** + * Harm severity levels. + * @public + */ +export enum HarmSeverity { + // Harm severity unspecified. + HARM_SEVERITY_UNSPECIFIED = 'HARM_SEVERITY_UNSPECIFIED', + // Negligible level of harm severity. + HARM_SEVERITY_NEGLIGIBLE = 'HARM_SEVERITY_NEGLIGIBLE', + // Low level of harm severity. + HARM_SEVERITY_LOW = 'HARM_SEVERITY_LOW', + // Medium level of harm severity. + HARM_SEVERITY_MEDIUM = 'HARM_SEVERITY_MEDIUM', + // High level of harm severity. + HARM_SEVERITY_HIGH = 'HARM_SEVERITY_HIGH' +} + /** * Reason that a prompt was blocked. * @public diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index 0ab2f14b8c1..7083341c1cd 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -16,7 +16,7 @@ */ import { Content } from './content'; -import { HarmBlockThreshold, HarmCategory } from './enums'; +import { HarmBlockMethod, HarmBlockThreshold, HarmCategory } from './enums'; /** * Base parameters for a number of methods. @@ -52,6 +52,7 @@ export interface GenerateContentRequest extends BaseParams { export interface SafetySetting { category: HarmCategory; threshold: HarmBlockThreshold; + method: HarmBlockMethod; } /** @@ -65,6 +66,8 @@ export interface GenerationConfig { temperature?: number; topP?: number; topK?: number; + presencePenalty?: number; + frequencyPenalty?: number; } /** diff --git a/packages/vertexai/src/types/responses.ts b/packages/vertexai/src/types/responses.ts index bdba5d28f23..375bd340e42 100644 --- a/packages/vertexai/src/types/responses.ts +++ b/packages/vertexai/src/types/responses.ts @@ -20,7 +20,8 @@ import { BlockReason, FinishReason, HarmCategory, - HarmProbability + HarmProbability, + HarmSeverity } from './enums'; /** @@ -101,6 +102,7 @@ export interface GenerateContentCandidate { finishMessage?: string; safetyRatings?: SafetyRating[]; citationMetadata?: CitationMetadata; + groundingMetadata?: GroundingMetadata; } /** @@ -124,6 +126,51 @@ export interface Citation { publicationDate?: Date; } +/** + * Metadata returned to client when grounding is enabled. + * @public + */ +export interface GroundingMetadata { + webSearchQueries?: string[]; + retrievalQueries?: string[]; + groundingAttributions: GroundingAttribution[]; +} + +/** + * @public + */ +export interface GroundingAttribution { + segment: Segment; + confidenceScore?: number; + web?: WebAttribution; + retrievedContext?: RetrievedContextAttribution; +} + +/** + * @public + */ +export interface Segment { + partIndex: number; + startIndex: number; + endIndex: number; +} + +/** + * @public + */ +export interface WebAttribution { + uri: string; + title: string; +} + +/** + * @public + */ +export interface RetrievedContextAttribution { + uri: string; + title: string; +} + /** * Protobuf google.type.Date * @public @@ -141,6 +188,10 @@ export interface Date { export interface SafetyRating { category: HarmCategory; probability: HarmProbability; + severity: HarmSeverity; + probabilityScore: number; + severityScore: number; + blocked: boolean; } /** @@ -148,5 +199,13 @@ export interface SafetyRating { * @public */ export interface CountTokensResponse { + /** + * The total number of tokens counted across all instances from the request. + */ totalTokens: number; + /** + * The total number of billable characters counted across all instances + * from the request. + */ + totalBillableCharacters?: number; } From 3d11ecc9786b0b5446b12f01556a3be6b3f25511 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 10 Apr 2024 13:02:46 -0700 Subject: [PATCH 14/28] Add systemInstruction and toolConfig (#8146) --- .../src/methods/chat-session-helpers.ts | 8 +- packages/vertexai/src/methods/chat-session.ts | 4 + .../src/methods/generate-content.test.ts | 4 +- .../src/models/generative-model.test.ts | 163 +++++++++++++++++- .../vertexai/src/models/generative-model.ts | 14 +- packages/vertexai/src/types/enums.ts | 21 ++- packages/vertexai/src/types/requests.ts | 29 +++- packages/vertexai/test-utils/mocks-lookup.ts | 57 ------ 8 files changed, 235 insertions(+), 65 deletions(-) delete mode 100644 packages/vertexai/test-utils/mocks-lookup.ts diff --git a/packages/vertexai/src/methods/chat-session-helpers.ts b/packages/vertexai/src/methods/chat-session-helpers.ts index b797c3071ff..0ac00ad0a1c 100644 --- a/packages/vertexai/src/methods/chat-session-helpers.ts +++ b/packages/vertexai/src/methods/chat-session-helpers.ts @@ -30,13 +30,17 @@ const VALID_PART_FIELDS: Array = [ const VALID_PARTS_PER_ROLE: { [key in Role]: Array } = { user: ['text', 'inlineData'], function: ['functionResponse'], - model: ['text', 'functionCall'] + model: ['text', 'functionCall'], + // System instructions shouldn't be in history anyway. + system: ['text'] }; const VALID_PREVIOUS_CONTENT_ROLES: { [key in Role]: Role[] } = { user: ['model'], function: ['model'], - model: ['user', 'function'] + model: ['user', 'function'], + // System instructions shouldn't be in history. + system: [] }; export function validateChatHistory(history: Content[]): void { diff --git a/packages/vertexai/src/methods/chat-session.ts b/packages/vertexai/src/methods/chat-session.ts index 2f49b1131e7..c685d84908a 100644 --- a/packages/vertexai/src/methods/chat-session.ts +++ b/packages/vertexai/src/methods/chat-session.ts @@ -82,6 +82,8 @@ export class ChatSession { safetySettings: this.params?.safetySettings, generationConfig: this.params?.generationConfig, tools: this.params?.tools, + toolConfig: this.params?.toolConfig, + systemInstruction: this.params?.systemInstruction, contents: [...this._history, newContent] }; let finalResult = {} as GenerateContentResult; @@ -135,6 +137,8 @@ export class ChatSession { safetySettings: this.params?.safetySettings, generationConfig: this.params?.generationConfig, tools: this.params?.tools, + toolConfig: this.params?.toolConfig, + systemInstruction: this.params?.systemInstruction, contents: [...this._history, newContent] }; const streamPromise = generateContentStream( diff --git a/packages/vertexai/src/methods/generate-content.test.ts b/packages/vertexai/src/methods/generate-content.test.ts index 4fdda9e40f8..5503c172c96 100644 --- a/packages/vertexai/src/methods/generate-content.test.ts +++ b/packages/vertexai/src/methods/generate-content.test.ts @@ -24,6 +24,7 @@ import * as request from '../requests/request'; import { generateContent } from './generate-content'; import { GenerateContentRequest, + HarmBlockMethod, HarmBlockThreshold, HarmCategory } from '../types'; @@ -47,7 +48,8 @@ const fakeRequestParams: GenerateContentRequest = { safetySettings: [ { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, - threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE + threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, + method: HarmBlockMethod.SEVERITY } ] }; diff --git a/packages/vertexai/src/models/generative-model.test.ts b/packages/vertexai/src/models/generative-model.test.ts index 5c930f36fc7..a97b7bfc003 100644 --- a/packages/vertexai/src/models/generative-model.test.ts +++ b/packages/vertexai/src/models/generative-model.test.ts @@ -14,9 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { expect } from 'chai'; +import { use, expect } from 'chai'; import { GenerativeModel } from './generative-model'; -import { VertexAI } from '../public-types'; +import { FunctionCallingMode, VertexAI } from '../public-types'; +import * as request from '../requests/request'; +import { match, restore, stub } from 'sinon'; +import { getMockResponse } from '../../test-utils/mock-response'; +import sinonChai from 'sinon-chai'; + +use(sinonChai); const fakeVertexAI: VertexAI = { app: { @@ -53,4 +59,157 @@ describe('GenerativeModel', () => { }); expect(genModel.model).to.equal('tunedModels/my-model'); }); + it('passes params through to generateContent', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.generateContent('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('myfunc') && + value.includes(FunctionCallingMode.NONE) && + value.includes('be friendly') + ); + }), + {} + ); + restore(); + }); + it('generateContent overrides model values', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.generateContent({ + contents: [{ role: 'user', parts: [{ text: 'hello' }] }], + tools: [{ functionDeclarations: [{ name: 'otherfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.AUTO } }, + systemInstruction: { role: 'system', parts: [{ text: 'be formal' }] } + }); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('otherfunc') && + value.includes(FunctionCallingMode.AUTO) && + value.includes('be formal') + ); + }), + {} + ); + restore(); + }); + it('passes params through to chat.sendMessage', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.startChat().sendMessage('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('myfunc') && + value.includes(FunctionCallingMode.NONE) && + value.includes('be friendly') + ); + }), + {} + ); + restore(); + }); + it('startChat overrides model values', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel + .startChat({ + tools: [{ functionDeclarations: [{ name: 'otherfunc' }] }], + toolConfig: { + functionCallingConfig: { mode: FunctionCallingMode.AUTO } + }, + systemInstruction: { role: 'system', parts: [{ text: 'be formal' }] } + }) + .sendMessage('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('otherfunc') && + value.includes(FunctionCallingMode.AUTO) && + value.includes('be formal') + ); + }), + {} + ); + restore(); + }); }); diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index 04f30036365..eec3297de9f 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -20,6 +20,7 @@ import { generateContentStream } from '../methods/generate-content'; import { + Content, CountTokensRequest, CountTokensResponse, GenerateContentRequest, @@ -31,7 +32,8 @@ import { RequestOptions, SafetySetting, StartChatParams, - Tool + Tool, + ToolConfig } from '../types'; import { ChatSession } from '../methods/chat-session'; import { countTokens } from '../methods/count-tokens'; @@ -52,6 +54,8 @@ export class GenerativeModel { safetySettings: SafetySetting[]; requestOptions?: RequestOptions; tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: Content; constructor( vertexAI: VertexAI, @@ -88,6 +92,8 @@ export class GenerativeModel { this.generationConfig = modelParams.generationConfig || {}; this.safetySettings = modelParams.safetySettings || []; this.tools = modelParams.tools; + this.toolConfig = modelParams.toolConfig; + this.systemInstruction = modelParams.systemInstruction; this.requestOptions = requestOptions || {}; } @@ -106,6 +112,8 @@ export class GenerativeModel { generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, + toolConfig: this.toolConfig, + systemInstruction: this.systemInstruction, ...formattedParams }, this.requestOptions @@ -129,6 +137,8 @@ export class GenerativeModel { generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, + toolConfig: this.toolConfig, + systemInstruction: this.systemInstruction, ...formattedParams }, this.requestOptions @@ -145,6 +155,8 @@ export class GenerativeModel { this.model, { tools: this.tools, + toolConfig: this.toolConfig, + systemInstruction: this.systemInstruction, ...startChatParams }, this.requestOptions diff --git a/packages/vertexai/src/types/enums.ts b/packages/vertexai/src/types/enums.ts index 817450a069b..2f3f655e8f0 100644 --- a/packages/vertexai/src/types/enums.ts +++ b/packages/vertexai/src/types/enums.ts @@ -25,7 +25,7 @@ export type Role = (typeof POSSIBLE_ROLES)[number]; * Possible roles. * @public */ -export const POSSIBLE_ROLES = ['user', 'model', 'function'] as const; +export const POSSIBLE_ROLES = ['user', 'model', 'function', 'system'] as const; /** * Harm categories that would cause prompts or candidates to be blocked. @@ -133,3 +133,22 @@ export enum FinishReason { // Unknown reason. OTHER = 'OTHER' } + +/** + * @public + */ +export enum FunctionCallingMode { + // Unspecified function calling mode. This value should not be used. + MODE_UNSPECIFIED = 'MODE_UNSPECIFIED', + // Default model behavior, model decides to predict either a function call + // or a natural language repspose. + 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". + ANY = 'ANY', + // Model will not predict any function call. Model behavior is same as when + // not passing any function declarations. + NONE = 'NONE' +} diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index 7083341c1cd..aa1e5bafa10 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -16,7 +16,12 @@ */ import { Content } from './content'; -import { HarmBlockMethod, HarmBlockThreshold, HarmCategory } from './enums'; +import { + FunctionCallingMode, + HarmBlockMethod, + HarmBlockThreshold, + HarmCategory +} from './enums'; /** * Base parameters for a number of methods. @@ -34,6 +39,8 @@ export interface BaseParams { export interface ModelParams extends BaseParams { model: string; tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: Content; } /** @@ -43,6 +50,8 @@ export interface ModelParams extends BaseParams { export interface GenerateContentRequest extends BaseParams { contents: Content[]; tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: Content; } /** @@ -77,6 +86,8 @@ export interface GenerationConfig { export interface StartChatParams extends BaseParams { history?: Content[]; tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: Content; } /** @@ -220,3 +231,19 @@ export interface FunctionDeclarationSchemaProperty { /** Optional. The example of the property. */ example?: unknown; } + +/** + * Tool config. This config is shared for all tools provided in the request. + * @public + */ +export interface ToolConfig { + functionCallingConfig: FunctionCallingConfig; +} + +/** + * @public + */ +export interface FunctionCallingConfig { + mode?: FunctionCallingMode; + allowedFunctionNames?: string[]; +} diff --git a/packages/vertexai/test-utils/mocks-lookup.ts b/packages/vertexai/test-utils/mocks-lookup.ts deleted file mode 100644 index 4dd8a5963d4..00000000000 --- a/packages/vertexai/test-utils/mocks-lookup.ts +++ /dev/null @@ -1,57 +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. - */ - -// Generated from mocks text files. - -export const mocksLookup: Record = { - 'streaming-failure-empty-content.txt': - 'data: {"candidates": [{"content": {},"index": 0}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', - 'streaming-failure-finish-reason-safety.txt': - 'data: {"candidates": [{"content": {"parts": [{"text": "No"}]},"finishReason": "SAFETY","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', - 'streaming-failure-prompt-blocked-safety.txt': - 'data: {"promptFeedback": {"blockReason": "SAFETY","safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', - 'streaming-failure-recitation-no-content.txt': - 'data: {"candidates": [{"content": {"parts": [{"text": "Copyrighted text goes here"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\ndata: {"candidates": [{"finishReason": "RECITATION","index": 0}]}\r\n\r\n', - 'streaming-success-basic-reply-long.txt': - 'data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat\'s health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog\'s health and detecting any potential health issues early on."}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\n', - 'streaming-success-basic-reply-short.txt': - 'data: {"candidates": [{"content": {"parts": [{"text": "Cheyenne"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', - 'streaming-success-citations.txt': - 'data: {"candidates": [{"content": {"parts": [{"text": "1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": ", and other quantities are quantized, meaning they can exist only in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \\"collapses\\" into a single state.\\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \\n\\n3. **Applications:**\\n - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \\n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \\n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \\n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\"Quantum Mechanics for Mathematicians\\" by James Glimm and Arthur Jaffe\\n - \\"Principles of Quantum Mechanics\\" by R. Shankar\\n - \\"Quantum Mechanics: Concepts and Applications\\" by Nouredine Zettili\\n - **Online Courses:**\\n - \\"Quantum Mechanics I\\" by MIT OpenCourseWare\\n - \\"Quantum Mechanics for Everyone\\" by Coursera\\n - \\"Quantum Mechanics\\" by Stanford Online\\n - **Documentaries and Videos:**\\n - \\"Quantum Mechanics: The Strangest Theory\\" (BBC Documentary)\\n - \\"Quantum Mechanics Explained Simply\\" by Veritasium (YouTube Channel)\\n - \\"What is Quantum Mechanics?\\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}\r\n\r\n', - 'streaming-success-function-call-short.txt': - 'data: {"candidates": [{"content": {"parts": [{ "functionCall": { "name": "getTemperature", "args": { "city": "San Jose" } } }]}, "finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\n', - 'streaming-success-utf8.txt': - 'data: { "candidates": [ { "content": { "parts": [ { "text": "秋风瑟瑟,叶落纷纷,\\n西风残照,寒" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ], "promptFeedback": { "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } }\r\n\r\ndata: { "candidates": [ { "content": { "parts": [ { "text": "霜渐浓。\\n枫叶红了,菊花黄了,\\n秋雨绵绵,秋意浓浓。\\n\\n秋夜漫漫,思" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] }\r\n\r\ndata: { "candidates": [ { "content": { "parts": [ { "text": "绪万千,\\n明月当空,星星眨眼。\\n思念远方的亲人,\\n祝愿他们幸福安康。\\n\\n秋天是收获的季节,\\n人们忙着收割庄稼,\\n为一年的辛劳画上圆满的句号。\\n秋天也是团圆的季节" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] }\r\n\r\ndata: { "candidates": [ { "content": { "parts": [ { "text": ",\\n一家人围坐在一起,\\n分享丰收的喜悦,共度美好时光。\\n\\n秋天是一个美丽的季节,\\n它有着独特的韵味,\\n让人沉醉其中,流连忘返。\\n让我们一起欣赏秋天的美景,\\n感受秋天的气息,领悟秋天的哲理。" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] }\r\n\r\n', - 'streaming-unknown-enum.txt': - 'data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\\n\\n- **Physical Characteristics:**\\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\\n\\n- **Behavior and Personality:**\\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\\n - Curious: Cats are curious creatures that love to explore their surroundings.\\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": "ls.\\n\\n- **Health and Care:**\\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat\'s health and detecting any potential health issues early on.\\n\\n**Dogs:**\\n\\n- **Physical Characteristics:**\\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\\n\\n- **Behavior and Personality:**\\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\r\n\r\ndata: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\\n\\n- **Health and Care:**\\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog\'s health and detecting any potential health issues early on."}]},"finishReason": "FAKE_ENUM","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM","probability": "NEGLIGIBLE_UNKNOWN_ENUM"}]}]}\r\n\r\n', - 'unary-failure-empty-content.json': - '{\n "candidates": [\n {\n "content": {},\n "index": 0\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', - 'unary-failure-finish-reason-safety.json': - '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "No"\n }\n ]\n },\n "finishReason": "SAFETY",\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "HIGH"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', - 'unary-failure-image-rejected.json': - '{\n "error": {\n "code": 400,\n "message": "Request contains an invalid argument.",\n "status": "INVALID_ARGUMENT",\n "details": [\n {\n "@type": "type.googleapis.com/google.rpc.DebugInfo",\n "detail": "[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short"\n }\n ]\n }\n}\n', - 'unary-failure-prompt-blocked-safety.json': - '{\n "promptFeedback": {\n "blockReason": "SAFETY",\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "HIGH"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', - 'unary-success-basic-reply-long.json': - '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won\'t extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing."\n }\n ]\n },\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', - 'unary-success-basic-reply-short.json': - '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "Helena"\n }\n ]\n },\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', - 'unary-success-citations.json': - '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "1. **Definition:**\\nQuantum mechanics is a fundamental theory in physics that provides the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum, and other quantities are quantized, meaning they can only exist in discrete values. \\n\\n2. **Key Concepts:**\\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \\n - **Uncertainty Principle:** Proposed by Werner Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \\n - **Quantum Superposition:** A quantum system can exist in multiple states simultaneously until it is measured. \\n - **Quantum Entanglement:** Two or more particles can become linked in such a way that the state of one affects the state of the others, regardless of the distance between them. \\n\\n3. **Implications and Applications:**\\n - **Atomic and Molecular Structure:** Quantum mechanics explains the structure of atoms, molecules, and chemical bonds. \\n - **Quantum Computing:** It enables the development of quantum computers that can solve certain computational problems much faster than classical computers. \\n - **Quantum Cryptography:** Quantum principles are used to develop secure communication methods. \\n - **Quantum Field Theory (QFT):** A relativistic theory that describes interactions between particles in quantum mechanics. It underpins the Standard Model of Physics. \\n - **Quantum Gravity:** Attempts to reconcile quantum mechanics with general relativity to explain the behavior of matter and gravity at very small scales. \\n\\n4. **Learning Resources:**\\n - **Books:**\\n - \\"Quantum Mechanics for Mathematicians\\" by James Glimm and Arthur Jaffe \\n - \\"Principles of Quantum Mechanics\\" by R. Shankar\\n - \\"Quantum Mechanics: Concepts and Applications\\" by Nouredine Zettili \\n - **Online Courses and Tutorials:**\\n - [Quantum Mechanics I](https://www.example.com) on Coursera\\n - [MIT OpenCourseWare](https://www.example.com) Quantum Physics I course materials \\n - [Khan Academy](https://www.example.com) Quantum Physics video tutorials \\n - **Videos and Documentaries:**\\n - [Quantum Mechanics - Crash Course Physics](https://www.example.com)\\n - [Quantum Mechanics: The Strangest Theory](https://www.example.com)\\n - [BBC: The Quantum World with Jim Al-Khalili](https://www.example.com)\\n - [NOVA: The Fabric of the Cosmos](https://www.example.com)"\n }\n ],\n "role": "model"\n },\n "finishReason": "STOP",\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ],\n "citationMetadata": {\n "citations": [\n {\n "startIndex": 574,\n "endIndex": 705,\n "uri": "https://www.example.com",\n "license": ""\n }\n ]\n }\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n}\n', - 'unary-unknown-enum.json': - '{\n "candidates": [\n {\n "content": {\n "parts": [\n {\n "text": "1. **Use Freshly Ground Coffee**:\\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\\n - Use a burr grinder for a consistent grind size.\\n\\n\\n2. **Choose the Right Water**:\\n - Use filtered or spring water for the best taste.\\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\\n\\n\\n3. **Measure Accurately**:\\n - Use a kitchen scale to measure your coffee and water precisely.\\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\\n\\n\\n4. **Preheat Your Equipment**:\\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\\n\\n\\n5. **Control the Water Temperature**:\\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\\n - Too hot water can extract bitter flavors, while too cold water won\'t extract enough flavor.\\n\\n\\n6. **Steep the Coffee**:\\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\\n\\n\\n7. **Clean Your Equipment**:\\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\\n\\n\\n8. **Experiment with Different Coffee Beans**:\\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\\n\\n\\n9. **Store Coffee Properly**:\\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\\n\\n\\n10. **Enjoy Freshly Brewed Coffee**:\\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing."\n }\n ]\n },\n "index": 0,\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT_ENUM_NEW",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT",\n "probability": "NEGLIGIBLE"\n }\n ]\n }\n ],\n "promptFeedback": {\n "safetyRatings": [\n {\n "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HATE_SPEECH",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_HARASSMENT",\n "probability": "NEGLIGIBLE"\n },\n {\n "category": "HARM_CATEGORY_DANGEROUS_CONTENT_LIKE_A_NEW_ENUM",\n "probability": "NEGLIGIBLE_NEW_ENUM"\n }\n ]\n }\n}\n' -}; From 1596af154c7b57fc3a3490d52181c7e31a7af9e5 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 22 Apr 2024 15:34:44 -0700 Subject: [PATCH 15/28] fix gl-js header (#8198) --- packages/vertexai/src/requests/request.test.ts | 4 +++- packages/vertexai/src/requests/request.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts index 0cd2d2686ce..6b90cac87d9 100644 --- a/packages/vertexai/src/requests/request.test.ts +++ b/packages/vertexai/src/requests/request.test.ts @@ -112,7 +112,9 @@ describe('request methods', () => { ); it('adds client headers', async () => { const headers = await getHeaders(fakeUrl); - expect(headers.get('x-goog-api-client')).to.include('gl-js/ fire/'); + expect(headers.get('x-goog-api-client')).to.match( + /gl-js\/[0-9\.]+ fire\/[0-9\.]+/ + ); }); it('adds api key', async () => { const headers = await getHeaders(fakeUrl); diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts index 58a22b4ed33..d6df5ce0b5b 100644 --- a/packages/vertexai/src/requests/request.ts +++ b/packages/vertexai/src/requests/request.ts @@ -71,7 +71,7 @@ export class RequestUrl { */ function getClientHeaders(): string { const loggingTags = []; - loggingTags.push(`${LANGUAGE_TAG}/`); + loggingTags.push(`${LANGUAGE_TAG}/${PACKAGE_VERSION}`); loggingTags.push(`fire/${PACKAGE_VERSION}`); return loggingTags.join(' '); } From 1aadc47ed6801494c78931d716e77192b591532c Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 22 Apr 2024 15:37:01 -0700 Subject: [PATCH 16/28] firebase/app version bump due to merge from master --- packages/vertexai/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index 98f31fc820c..fb80eacd7c5 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -51,7 +51,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app": "0.10.0", + "@firebase/app": "0.10.1", "@rollup/plugin-json": "4.1.0", "rollup": "2.79.1", "rollup-plugin-replace": "2.2.0", @@ -73,4 +73,4 @@ ], "reportDir": "./coverage/node" } -} +} \ No newline at end of file From 627b561adba2fdbe190c282ead7b1454e7b0207f Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 29 Apr 2024 11:34:49 -0700 Subject: [PATCH 17/28] Allow text-only systemInstruction (#8208) --- .../src/models/generative-model.test.ts | 50 +++++ .../vertexai/src/models/generative-model.ts | 9 +- .../src/requests/request-helpers.test.ts | 175 ++++++++++++++++++ .../vertexai/src/requests/request-helpers.ts | 31 +++- packages/vertexai/src/types/requests.ts | 8 +- 5 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 packages/vertexai/src/requests/request-helpers.test.ts diff --git a/packages/vertexai/src/models/generative-model.test.ts b/packages/vertexai/src/models/generative-model.test.ts index a97b7bfc003..7b0287492da 100644 --- a/packages/vertexai/src/models/generative-model.test.ts +++ b/packages/vertexai/src/models/generative-model.test.ts @@ -94,6 +94,31 @@ describe('GenerativeModel', () => { ); restore(); }); + it('passes text-only systemInstruction through to generateContent', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + systemInstruction: 'be friendly' + }); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.generateContent('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return value.includes('be friendly'); + }), + {} + ); + restore(); + }); it('generateContent overrides model values', async () => { const genModel = new GenerativeModel(fakeVertexAI, { model: 'my-model', @@ -169,6 +194,31 @@ describe('GenerativeModel', () => { ); restore(); }); + it('passes text-only systemInstruction through to chat.sendMessage', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + systemInstruction: 'be friendly' + }); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.startChat().sendMessage('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return value.includes('be friendly'); + }), + {} + ); + restore(); + }); it('startChat overrides model values', async () => { const genModel = new GenerativeModel(fakeVertexAI, { model: 'my-model', diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index eec3297de9f..f68bc00b295 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -37,7 +37,10 @@ import { } from '../types'; import { ChatSession } from '../methods/chat-session'; import { countTokens } from '../methods/count-tokens'; -import { formatGenerateContentInput } from '../requests/request-helpers'; +import { + formatGenerateContentInput, + formatSystemInstruction +} from '../requests/request-helpers'; import { VertexAI } from '../public-types'; import { ERROR_FACTORY, VertexError } from '../errors'; import { ApiSettings } from '../types/internal'; @@ -93,7 +96,9 @@ export class GenerativeModel { this.safetySettings = modelParams.safetySettings || []; this.tools = modelParams.tools; this.toolConfig = modelParams.toolConfig; - this.systemInstruction = modelParams.systemInstruction; + this.systemInstruction = formatSystemInstruction( + modelParams.systemInstruction + ); this.requestOptions = requestOptions || {}; } diff --git a/packages/vertexai/src/requests/request-helpers.test.ts b/packages/vertexai/src/requests/request-helpers.test.ts new file mode 100644 index 00000000000..41278ee657d --- /dev/null +++ b/packages/vertexai/src/requests/request-helpers.test.ts @@ -0,0 +1,175 @@ +/** + * @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. + */ + +import { expect, use } from 'chai'; +import sinonChai from 'sinon-chai'; +import { Content } from '../types'; +import { formatGenerateContentInput } from './request-helpers'; + +use(sinonChai); + +describe('request formatting methods', () => { + describe('formatGenerateContentInput', () => { + it('formats a text string into a request', () => { + const result = formatGenerateContentInput('some text content'); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'some text content' }] + } + ] + }); + }); + it('formats an array of strings into a request', () => { + const result = formatGenerateContentInput(['txt1', 'txt2']); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txt1' }, { text: 'txt2' }] + } + ] + }); + }); + it('formats an array of Parts into a request', () => { + const result = formatGenerateContentInput([ + { text: 'txt1' }, + { text: 'txtB' } + ]); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txt1' }, { text: 'txtB' }] + } + ] + }); + }); + it('formats a mixed array into a request', () => { + const result = formatGenerateContentInput(['txtA', { text: 'txtB' }]); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }, { text: 'txtB' }] + } + ] + }); + }); + it('preserves other properties of request', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + generationConfig: { topK: 100 } + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + generationConfig: { topK: 100 } + }); + }); + it('formats systemInstructions if provided as text', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: 'be excited' + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + it('formats systemInstructions if provided as Part', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { text: 'be excited' } + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + it('formats systemInstructions if provided as Content (no role)', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { parts: [{ text: 'be excited' }] } as Content + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + it('passes thru systemInstructions if provided as Content', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + }); +}); diff --git a/packages/vertexai/src/requests/request-helpers.ts b/packages/vertexai/src/requests/request-helpers.ts index 96f4ffb1ea7..0b7ce4ed4d2 100644 --- a/packages/vertexai/src/requests/request-helpers.ts +++ b/packages/vertexai/src/requests/request-helpers.ts @@ -18,6 +18,25 @@ import { Content, GenerateContentRequest, Part } from '../types'; import { ERROR_FACTORY, VertexError } from '../errors'; +export function formatSystemInstruction( + input?: string | Part | Content +): Content | undefined { + // null or undefined + if (input == null) { + return undefined; + } else if (typeof input === 'string') { + return { role: 'system', parts: [{ text: input }] } as Content; + } else if ((input as Part).text) { + return { role: 'system', parts: [input as Part] }; + } else if ((input as Content).parts) { + if (!(input as Content).role) { + return { role: 'system', parts: (input as Content).parts }; + } else { + return input as Content; + } + } +} + export function formatNewContent( request: string | Array ): Content { @@ -84,10 +103,18 @@ function assignRoleToPartsAndValidateSendMessageRequest( export function formatGenerateContentInput( params: GenerateContentRequest | string | Array ): GenerateContentRequest { + let formattedRequest: GenerateContentRequest; if ((params as GenerateContentRequest).contents) { - return params as GenerateContentRequest; + formattedRequest = params as GenerateContentRequest; } else { + // Array or string const content = formatNewContent(params as string | Array); - return { contents: [content] }; + formattedRequest = { contents: [content] }; + } + if ((params as GenerateContentRequest).systemInstruction) { + formattedRequest.systemInstruction = formatSystemInstruction( + (params as GenerateContentRequest).systemInstruction + ); } + return formattedRequest; } diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index aa1e5bafa10..70ce881ff8a 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Content } from './content'; +import { Content, Part } from './content'; import { FunctionCallingMode, HarmBlockMethod, @@ -40,7 +40,7 @@ export interface ModelParams extends BaseParams { model: string; tools?: Tool[]; toolConfig?: ToolConfig; - systemInstruction?: Content; + systemInstruction?: string | Part | Content; } /** @@ -51,7 +51,7 @@ export interface GenerateContentRequest extends BaseParams { contents: Content[]; tools?: Tool[]; toolConfig?: ToolConfig; - systemInstruction?: Content; + systemInstruction?: string | Part | Content; } /** @@ -87,7 +87,7 @@ export interface StartChatParams extends BaseParams { history?: Content[]; tools?: Tool[]; toolConfig?: ToolConfig; - systemInstruction?: Content; + systemInstruction?: string | Part | Content; } /** From b981a4540e0065b8924ff7ec6fd6a5fc3bce9bda Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 2 May 2024 11:35:58 -0700 Subject: [PATCH 18/28] update package versions after merge --- packages/vertexai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index fb80eacd7c5..4ae076cdda2 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -51,7 +51,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app": "0.10.1", + "@firebase/app": "0.10.2", "@rollup/plugin-json": "4.1.0", "rollup": "2.79.1", "rollup-plugin-replace": "2.2.0", From 964bd26e34238c3fd058b21a41b826d867a33b14 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 6 May 2024 11:20:10 -0700 Subject: [PATCH 19/28] [VertexAI] Allow user to set location (#8219) --- packages/vertexai/src/api.ts | 9 ++-- packages/vertexai/src/index.ts | 4 +- packages/vertexai/src/public-types.ts | 8 ++++ .../src/requests/stream-reader.test.ts | 17 ++++---- packages/vertexai/src/service.test.ts | 43 +++++++++++++++++++ packages/vertexai/src/service.ts | 8 ++-- 6 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 packages/vertexai/src/service.test.ts diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 19d250ccb79..3702a5ff17f 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -20,7 +20,7 @@ import { Provider } from '@firebase/component'; import { getModularInstance } from '@firebase/util'; import { DEFAULT_LOCATION, VERTEX_TYPE } from './constants'; import { VertexAIService } from './service'; -import { VertexAI } from './public-types'; +import { VertexAI, VertexAIOptions } from './public-types'; import { ERROR_FACTORY, VertexError } from './errors'; import { ModelParams, RequestOptions } from './types'; import { GenerativeModel } from './models/generative-model'; @@ -42,13 +42,16 @@ declare module '@firebase/component' { * * @param app - The {@link @firebase/app#FirebaseApp} to use. */ -export function getVertexAI(app: FirebaseApp = getApp()): VertexAI { +export function getVertexAI( + app: FirebaseApp = getApp(), + options?: VertexAIOptions +): VertexAI { app = getModularInstance(app); // Dependencies const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); return vertexProvider.getImmediate({ - identifier: DEFAULT_LOCATION + identifier: options?.location || DEFAULT_LOCATION }); } diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index 403d690a370..9a0c717b9ee 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -37,11 +37,11 @@ function registerVertex(): void { _registerComponent( new Component( VERTEX_TYPE, - container => { + (container, { instanceIdentifier: location }) => { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app').getImmediate(); const appCheckProvider = container.getProvider('app-check-internal'); - return new VertexAIService(app, appCheckProvider); + return new VertexAIService(app, appCheckProvider, { location }); }, ComponentType.PUBLIC ).setMultipleInstances(true) diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index a662580da86..64eeccba371 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -30,3 +30,11 @@ export interface VertexAI { app: FirebaseApp; location: string; } + +/** + * Options when initializing the Firebase Vertex AI SDK. + * @public + */ +export interface VertexAIOptions { + location?: string; +} diff --git a/packages/vertexai/src/requests/stream-reader.test.ts b/packages/vertexai/src/requests/stream-reader.test.ts index e942bb9f2b6..ae6d9fd33e4 100644 --- a/packages/vertexai/src/requests/stream-reader.test.ts +++ b/packages/vertexai/src/requests/stream-reader.test.ts @@ -32,7 +32,8 @@ import { FinishReason, GenerateContentResponse, HarmCategory, - HarmProbability + HarmProbability, + SafetyRating } from '../types'; use(sinonChai); @@ -229,7 +230,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, probability: HarmProbability.LOW - } + } as SafetyRating ] } } @@ -256,7 +257,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_HARASSMENT, probability: HarmProbability.NEGLIGIBLE - } + } as SafetyRating ] } ], @@ -266,7 +267,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, probability: HarmProbability.LOW - } + } as SafetyRating ] } }, @@ -284,7 +285,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_HARASSMENT, probability: HarmProbability.NEGLIGIBLE - } + } as SafetyRating ], citationMetadata: { citations: [ @@ -304,7 +305,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, probability: HarmProbability.HIGH - } + } as SafetyRating ] } }, @@ -322,7 +323,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, probability: HarmProbability.MEDIUM - } + } as SafetyRating ], citationMetadata: { citations: [ @@ -348,7 +349,7 @@ describe('aggregateResponses', () => { { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, probability: HarmProbability.HIGH - } + } as SafetyRating ] } } diff --git a/packages/vertexai/src/service.test.ts b/packages/vertexai/src/service.test.ts new file mode 100644 index 00000000000..7abe6a4019d --- /dev/null +++ b/packages/vertexai/src/service.test.ts @@ -0,0 +1,43 @@ +/** + * @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. + */ +import { DEFAULT_LOCATION } from './constants'; +import { VertexAIService } from './service'; +import { expect } from 'chai'; + +const fakeApp = { + name: 'DEFAULT', + automaticDataCollectionEnabled: true, + options: { + apiKey: 'key', + projectId: 'my-project' + } +}; + +describe('VertexAIService', () => { + it('uses default location if not specified', () => { + const vertexAI = new VertexAIService(fakeApp); + expect(vertexAI.location).to.equal(DEFAULT_LOCATION); + }); + it('uses custom location if specified', () => { + const vertexAI = new VertexAIService( + fakeApp, + /* appCheckProvider */ undefined, + { location: 'somewhere' } + ); + expect(vertexAI.location).to.equal('somewhere'); + }); +}); diff --git a/packages/vertexai/src/service.ts b/packages/vertexai/src/service.ts index 6925deb2a8c..a061fc4ad65 100644 --- a/packages/vertexai/src/service.ts +++ b/packages/vertexai/src/service.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp, _FirebaseService } from '@firebase/app'; -import { VertexAI } from './public-types'; +import { VertexAI, VertexAIOptions } from './public-types'; import { AppCheckInternalComponentName, FirebaseAppCheckInternal @@ -30,12 +30,12 @@ export class VertexAIService implements VertexAI, _FirebaseService { constructor( public app: FirebaseApp, - appCheckProvider?: Provider + appCheckProvider?: Provider, + public options?: VertexAIOptions ) { const appCheck = appCheckProvider?.getImmediate({ optional: true }); this.appCheck = appCheck || null; - // TODO: add in user-set location option when that feature is available - this.location = DEFAULT_LOCATION; + this.location = this.options?.location || DEFAULT_LOCATION; } _delete(): Promise { From 070e0cc18235f2763d0e12e772929d7c74271ba1 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 6 May 2024 15:14:17 -0400 Subject: [PATCH 20/28] Integrate VertexAI with Auth (#8216) --- packages/vertexai/src/index.ts | 3 +- .../vertexai/src/models/generative-model.ts | 5 +++ .../vertexai/src/requests/request.test.ts | 37 +++++++++++++++++++ packages/vertexai/src/requests/request.ts | 8 ++++ packages/vertexai/src/service.test.ts | 1 + packages/vertexai/src/service.ts | 8 ++++ packages/vertexai/src/types/internal.ts | 2 + 7 files changed, 63 insertions(+), 1 deletion(-) diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index 9a0c717b9ee..507d3e38697 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -40,8 +40,9 @@ function registerVertex(): void { (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, appCheckProvider, { location }); + return new VertexAIService(app, auth, appCheckProvider, { location }); }, ComponentType.PUBLIC ).setMultipleInstances(true) diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index f68bc00b295..efd6719661b 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -79,6 +79,11 @@ export class GenerativeModel { this._apiSettings.getAppCheckToken = () => (vertexAI as VertexAIService).appCheck!.getToken(); } + + if ((vertexAI as VertexAIService).auth) { + this._apiSettings.getAuthToken = () => + (vertexAI as VertexAIService).auth!.getToken(); + } } if (modelParams.model.includes('/')) { if (modelParams.model.startsWith('models/')) { diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts index 6b90cac87d9..d27c4e41252 100644 --- a/packages/vertexai/src/requests/request.test.ts +++ b/packages/vertexai/src/requests/request.test.ts @@ -101,6 +101,7 @@ describe('request methods', () => { apiKey: 'key', project: 'myproject', location: 'moon', + getAuthToken: () => Promise.resolve({ accessToken: 'authtoken' }), getAppCheckToken: () => Promise.resolve({ token: 'appchecktoken' }) }; const fakeUrl = new RequestUrl( @@ -173,6 +174,42 @@ describe('request methods', () => { const headers = await getHeaders(fakeUrl); expect(headers.has('X-Firebase-AppCheck')).to.be.false; }); + it('adds auth token if it exists', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('Authorization')).to.equal('Firebase authtoken'); + }); + it('ignores auth token header if no auth service', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon' + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('Authorization')).to.be.false; + }); + it('ignores auth token header if returned token was undefined', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon', + //@ts-ignore + getAppCheckToken: () => Promise.resolve() + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('Authorization')).to.be.false; + }); }); describe('makeRequest', () => { it('no error', async () => { diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts index d6df5ce0b5b..ca78c16a383 100644 --- a/packages/vertexai/src/requests/request.ts +++ b/packages/vertexai/src/requests/request.ts @@ -87,6 +87,14 @@ export async function getHeaders(url: RequestUrl): Promise { 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; } diff --git a/packages/vertexai/src/service.test.ts b/packages/vertexai/src/service.test.ts index 7abe6a4019d..d3487e9bdd2 100644 --- a/packages/vertexai/src/service.test.ts +++ b/packages/vertexai/src/service.test.ts @@ -35,6 +35,7 @@ describe('VertexAIService', () => { it('uses custom location if specified', () => { const vertexAI = new VertexAIService( fakeApp, + /* authProvider */ undefined, /* appCheckProvider */ undefined, { location: 'somewhere' } ); diff --git a/packages/vertexai/src/service.ts b/packages/vertexai/src/service.ts index a061fc4ad65..05b2d559e58 100644 --- a/packages/vertexai/src/service.ts +++ b/packages/vertexai/src/service.ts @@ -22,18 +22,26 @@ import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; import { Provider } from '@firebase/component'; +import { + FirebaseAuthInternal, + FirebaseAuthInternalName +} from '@firebase/auth-interop-types'; import { DEFAULT_LOCATION } from './constants'; export class VertexAIService implements VertexAI, _FirebaseService { + auth: FirebaseAuthInternal | null; appCheck: FirebaseAppCheckInternal | null; location: string; constructor( public app: FirebaseApp, + authProvider?: Provider, appCheckProvider?: Provider, public options?: VertexAIOptions ) { const appCheck = appCheckProvider?.getImmediate({ optional: true }); + const auth = authProvider?.getImmediate({ optional: true }); + this.auth = auth || null; this.appCheck = appCheck || null; this.location = this.options?.location || DEFAULT_LOCATION; } diff --git a/packages/vertexai/src/types/internal.ts b/packages/vertexai/src/types/internal.ts index 57c2e3ae160..8271175feff 100644 --- a/packages/vertexai/src/types/internal.ts +++ b/packages/vertexai/src/types/internal.ts @@ -16,10 +16,12 @@ */ import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; export interface ApiSettings { apiKey: string; project: string; location: string; + getAuthToken?: () => Promise; getAppCheckToken?: () => Promise; } From 774570a99f3e02fa2801a9d243afa5b27fa3b1d4 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 6 May 2024 15:18:20 -0400 Subject: [PATCH 21/28] Add support for GCS FileData in inference (#8223) --- .../src/requests/request-helpers.test.ts | 29 ++++++++++++++++++- packages/vertexai/src/types/content.ts | 24 ++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/src/requests/request-helpers.test.ts b/packages/vertexai/src/requests/request-helpers.test.ts index 41278ee657d..d0dfbe63fc5 100644 --- a/packages/vertexai/src/requests/request-helpers.test.ts +++ b/packages/vertexai/src/requests/request-helpers.test.ts @@ -170,6 +170,33 @@ describe('request formatting methods', () => { ], systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } }); - }); + }), + it('formats fileData as part if provided as part', () => { + const result = formatGenerateContentInput([ + 'What is this?', + { + fileData: { + mimeType: 'image/jpeg', + fileUri: 'gs://sample.appspot.com/image.jpeg' + } + } + ]); + expect(result).to.be.deep.equal({ + contents: [ + { + role: 'user', + parts: [ + { + fileData: { + mimeType: 'image/jpeg', + fileUri: 'gs://sample.appspot.com/image.jpeg' + } + }, + { text: 'What is this?' } + ] + } + ] + }); + }); }); }); diff --git a/packages/vertexai/src/types/content.ts b/packages/vertexai/src/types/content.ts index c626ce75fc9..168eeda0ee9 100644 --- a/packages/vertexai/src/types/content.ts +++ b/packages/vertexai/src/types/content.ts @@ -34,7 +34,8 @@ export type Part = | TextPart | InlineDataPart | FunctionCallPart - | FunctionResponsePart; + | FunctionResponsePart + | FileDataPart; /** * Content part interface if the part represents a text string. @@ -101,6 +102,18 @@ export interface FunctionResponsePart { functionResponse: FunctionResponse; } +/** + * Content part interface if the part represents {@link FileData} + * @public + */ +export interface FileDataPart { + text?: never; + inlineData?: never; + functionCall?: never; + functionResponse?: never; + fileData: FileData; +} + /** * A predicted [FunctionCall] returned from the model * that contains a string representing the [FunctionDeclaration.name] @@ -137,3 +150,12 @@ export interface GenerativeContentBlob { */ data: string; } + +/** + * Data pointing to a file uploaded on Google Cloud Storage. + * @public + */ +export interface FileData { + mimeType: string; + fileUri: string; +} From e12d91f7b918fc564db0e10cb028403cb4096bb4 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 6 May 2024 12:55:16 -0700 Subject: [PATCH 22/28] Prepare for public preview release (#8214) --- .changeset/olive-points-fold.md | 7 + .../workflows/prerelease-manual-deploy.yml | 1 + .github/workflows/release-prod.yml | 1 + .github/workflows/release-staging.yml | 1 + common/api-review/vertexai-preview.api.md | 584 ++++++++++++++++++ packages/firebase/package.json | 18 +- .../{vertexai => vertexai-preview}/index.ts | 2 +- .../package.json | 4 +- packages/vertexai/package.json | 11 +- packages/vertexai/src/api.ts | 12 +- packages/vertexai/src/constants.ts | 2 +- packages/vertexai/src/errors.ts | 6 +- packages/vertexai/src/types/content.ts | 2 +- packages/vertexai/src/types/requests.ts | 2 +- packages/vertexai/src/types/responses.ts | 5 + scripts/docgen/docgen.ts | 3 +- 16 files changed, 634 insertions(+), 27 deletions(-) create mode 100644 .changeset/olive-points-fold.md create mode 100644 common/api-review/vertexai-preview.api.md rename packages/firebase/{vertexai => vertexai-preview}/index.ts (93%) rename packages/firebase/{vertexai => vertexai-preview}/package.json (55%) diff --git a/.changeset/olive-points-fold.md b/.changeset/olive-points-fold.md new file mode 100644 index 00000000000..eb58ac3f922 --- /dev/null +++ b/.changeset/olive-points-fold.md @@ -0,0 +1,7 @@ +--- +'firebase': minor +'@firebase/vertexai-preview': patch +'@firebase/app': patch +--- + +Add the preview version of the VertexAI SDK. diff --git a/.github/workflows/prerelease-manual-deploy.yml b/.github/workflows/prerelease-manual-deploy.yml index c60302c1eac..a1dfc08d847 100644 --- a/.github/workflows/prerelease-manual-deploy.yml +++ b/.github/workflows/prerelease-manual-deploy.yml @@ -77,6 +77,7 @@ jobs: NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} + NPM_TOKEN_VERTEXAI_PREVIEW: ${{secrets.NPM_TOKEN_VERTEXAI_PREVIEW}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index ed524abd4ad..6dcedb0b04a 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -87,6 +87,7 @@ jobs: NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} + NPM_TOKEN_VERTEXAI_PREVIEW: ${{secrets.NPM_TOKEN_VERTEXAI_PREVIEW}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index 699113effa6..32eca1f036d 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -113,6 +113,7 @@ jobs: NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} + NPM_TOKEN_VERTEXAI_PREVIEW: ${{secrets.NPM_TOKEN_VERTEXAI_PREVIEW}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/common/api-review/vertexai-preview.api.md b/common/api-review/vertexai-preview.api.md new file mode 100644 index 00000000000..0e1029bd715 --- /dev/null +++ b/common/api-review/vertexai-preview.api.md @@ -0,0 +1,584 @@ +## API Report File for "@firebase/vertexai-preview" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; +import { FirebaseApp } from '@firebase/app'; + +// @public +export interface BaseParams { + // (undocumented) + generationConfig?: GenerationConfig; + // (undocumented) + safetySettings?: SafetySetting[]; +} + +// @public +export enum BlockReason { + // (undocumented) + BLOCKED_REASON_UNSPECIFIED = "BLOCKED_REASON_UNSPECIFIED", + // (undocumented) + OTHER = "OTHER", + // (undocumented) + SAFETY = "SAFETY" +} + +// @public +export class ChatSession { + // Warning: (ae-forgotten-export) The symbol "ApiSettings" needs to be exported by the entry point index.d.ts + constructor(apiSettings: ApiSettings, model: string, params?: StartChatParams | undefined, requestOptions?: RequestOptions | undefined); + getHistory(): Promise; + // (undocumented) + model: string; + // (undocumented) + params?: StartChatParams | undefined; + // (undocumented) + requestOptions?: RequestOptions | undefined; + sendMessage(request: string | Array): Promise; + sendMessageStream(request: string | Array): Promise; + } + +// @public +export interface Citation { + // (undocumented) + endIndex?: number; + // (undocumented) + license?: string; + // (undocumented) + publicationDate?: Date_2; + // (undocumented) + startIndex?: number; + // (undocumented) + title?: string; + // (undocumented) + uri?: string; +} + +// @public +export interface CitationMetadata { + // (undocumented) + citations: Citation[]; +} + +// @public +export interface Content { + // (undocumented) + parts: Part[]; + // (undocumented) + role: Role; +} + +// @public +export interface CountTokensRequest { + // (undocumented) + contents: Content[]; +} + +// @public +export interface CountTokensResponse { + totalBillableCharacters?: number; + totalTokens: number; +} + +// @public +interface Date_2 { + // (undocumented) + day: number; + // (undocumented) + month: number; + // (undocumented) + year: number; +} + +export { Date_2 as Date } + +// @public +export interface EnhancedGenerateContentResponse extends GenerateContentResponse { + // (undocumented) + functionCalls: () => FunctionCall[] | undefined; + text: () => string; +} + +// @public +export enum FinishReason { + // (undocumented) + FINISH_REASON_UNSPECIFIED = "FINISH_REASON_UNSPECIFIED", + // (undocumented) + MAX_TOKENS = "MAX_TOKENS", + // (undocumented) + OTHER = "OTHER", + // (undocumented) + RECITATION = "RECITATION", + // (undocumented) + SAFETY = "SAFETY", + // (undocumented) + STOP = "STOP" +} + +// @public +export interface FunctionCall { + // (undocumented) + args: object; + // (undocumented) + name: string; +} + +// @public (undocumented) +export interface FunctionCallingConfig { + // (undocumented) + allowedFunctionNames?: string[]; + // (undocumented) + mode?: FunctionCallingMode; +} + +// @public (undocumented) +export enum FunctionCallingMode { + // (undocumented) + ANY = "ANY", + // (undocumented) + AUTO = "AUTO", + // (undocumented) + MODE_UNSPECIFIED = "MODE_UNSPECIFIED", + // (undocumented) + NONE = "NONE" +} + +// @public +export interface FunctionCallPart { + // (undocumented) + functionCall: FunctionCall; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData?: never; + // (undocumented) + text?: never; +} + +// @public +export interface FunctionDeclaration { + description?: string; + name: string; + parameters?: FunctionDeclarationSchema; +} + +// @public +export interface FunctionDeclarationSchema { + description?: string; + properties: { + [k: string]: FunctionDeclarationSchemaProperty; + }; + required?: string[]; + type: FunctionDeclarationSchemaType; +} + +// @public +export interface FunctionDeclarationSchemaProperty { + description?: string; + enum?: string[]; + example?: unknown; + format?: string; + items?: FunctionDeclarationSchema; + nullable?: boolean; + properties?: { + [k: string]: FunctionDeclarationSchema; + }; + required?: string[]; + type?: FunctionDeclarationSchemaType; +} + +// @public +export enum FunctionDeclarationSchemaType { + ARRAY = "ARRAY", + BOOLEAN = "BOOLEAN", + INTEGER = "INTEGER", + NUMBER = "NUMBER", + OBJECT = "OBJECT", + STRING = "STRING" +} + +// @public +export interface FunctionDeclarationsTool { + functionDeclarations?: FunctionDeclaration[]; +} + +// @public +export interface FunctionResponse { + // (undocumented) + name: string; + // (undocumented) + response: object; +} + +// @public +export interface FunctionResponsePart { + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse: FunctionResponse; + // (undocumented) + inlineData?: never; + // (undocumented) + text?: never; +} + +// @public +export interface GenerateContentCandidate { + // (undocumented) + citationMetadata?: CitationMetadata; + // (undocumented) + content: Content; + // (undocumented) + finishMessage?: string; + // (undocumented) + finishReason?: FinishReason; + // (undocumented) + groundingMetadata?: GroundingMetadata; + // (undocumented) + index: number; + // (undocumented) + safetyRatings?: SafetyRating[]; +} + +// @public +export interface GenerateContentRequest extends BaseParams { + // (undocumented) + contents: Content[]; + // (undocumented) + systemInstruction?: string | Part | Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export interface GenerateContentResponse { + // (undocumented) + candidates?: GenerateContentCandidate[]; + // (undocumented) + promptFeedback?: PromptFeedback; + // (undocumented) + usageMetadata?: UsageMetadata; +} + +// @public +export interface GenerateContentResult { + // (undocumented) + response: EnhancedGenerateContentResponse; +} + +// @public +export interface GenerateContentStreamResult { + // (undocumented) + response: Promise; + // (undocumented) + stream: AsyncGenerator; +} + +// @public +export interface GenerationConfig { + // (undocumented) + candidateCount?: number; + // (undocumented) + frequencyPenalty?: number; + // (undocumented) + maxOutputTokens?: number; + // (undocumented) + presencePenalty?: number; + // (undocumented) + stopSequences?: string[]; + // (undocumented) + temperature?: number; + // (undocumented) + topK?: number; + // (undocumented) + topP?: number; +} + +// @public +export interface GenerativeContentBlob { + data: string; + // (undocumented) + mimeType: string; +} + +// @public +export class GenerativeModel { + constructor(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions); + countTokens(request: CountTokensRequest | string | Array): Promise; + generateContent(request: GenerateContentRequest | string | Array): Promise; + generateContentStream(request: GenerateContentRequest | string | Array): Promise; + // (undocumented) + generationConfig: GenerationConfig; + // (undocumented) + model: string; + // (undocumented) + requestOptions?: RequestOptions; + // (undocumented) + safetySettings: SafetySetting[]; + startChat(startChatParams?: StartChatParams): ChatSession; + // (undocumented) + systemInstruction?: Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export function getGenerativeModel(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions): GenerativeModel; + +// @public +export function getVertexAI(app?: FirebaseApp): VertexAI; + +// @public (undocumented) +export interface GroundingAttribution { + // (undocumented) + confidenceScore?: number; + // (undocumented) + retrievedContext?: RetrievedContextAttribution; + // (undocumented) + segment: Segment; + // (undocumented) + web?: WebAttribution; +} + +// @public +export interface GroundingMetadata { + // (undocumented) + groundingAttributions: GroundingAttribution[]; + // (undocumented) + retrievalQueries?: string[]; + // (undocumented) + webSearchQueries?: string[]; +} + +// @public (undocumented) +export enum HarmBlockMethod { + // (undocumented) + HARM_BLOCK_METHOD_UNSPECIFIED = "HARM_BLOCK_METHOD_UNSPECIFIED", + // (undocumented) + PROBABILITY = "PROBABILITY", + // (undocumented) + SEVERITY = "SEVERITY" +} + +// @public +export enum HarmBlockThreshold { + // (undocumented) + BLOCK_LOW_AND_ABOVE = "BLOCK_LOW_AND_ABOVE", + // (undocumented) + BLOCK_MEDIUM_AND_ABOVE = "BLOCK_MEDIUM_AND_ABOVE", + // (undocumented) + BLOCK_NONE = "BLOCK_NONE", + // (undocumented) + BLOCK_ONLY_HIGH = "BLOCK_ONLY_HIGH", + // (undocumented) + HARM_BLOCK_THRESHOLD_UNSPECIFIED = "HARM_BLOCK_THRESHOLD_UNSPECIFIED" +} + +// @public +export enum HarmCategory { + // (undocumented) + HARM_CATEGORY_DANGEROUS_CONTENT = "HARM_CATEGORY_DANGEROUS_CONTENT", + // (undocumented) + HARM_CATEGORY_HARASSMENT = "HARM_CATEGORY_HARASSMENT", + // (undocumented) + HARM_CATEGORY_HATE_SPEECH = "HARM_CATEGORY_HATE_SPEECH", + // (undocumented) + HARM_CATEGORY_SEXUALLY_EXPLICIT = "HARM_CATEGORY_SEXUALLY_EXPLICIT", + // (undocumented) + HARM_CATEGORY_UNSPECIFIED = "HARM_CATEGORY_UNSPECIFIED" +} + +// @public +export enum HarmProbability { + // (undocumented) + HARM_PROBABILITY_UNSPECIFIED = "HARM_PROBABILITY_UNSPECIFIED", + // (undocumented) + HIGH = "HIGH", + // (undocumented) + LOW = "LOW", + // (undocumented) + MEDIUM = "MEDIUM", + // (undocumented) + NEGLIGIBLE = "NEGLIGIBLE" +} + +// @public +export enum HarmSeverity { + // (undocumented) + HARM_SEVERITY_HIGH = "HARM_SEVERITY_HIGH", + // (undocumented) + HARM_SEVERITY_LOW = "HARM_SEVERITY_LOW", + // (undocumented) + HARM_SEVERITY_MEDIUM = "HARM_SEVERITY_MEDIUM", + // (undocumented) + HARM_SEVERITY_NEGLIGIBLE = "HARM_SEVERITY_NEGLIGIBLE", + // (undocumented) + HARM_SEVERITY_UNSPECIFIED = "HARM_SEVERITY_UNSPECIFIED" +} + +// @public +export interface InlineDataPart { + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData: GenerativeContentBlob; + // (undocumented) + text?: never; + videoMetadata?: VideoMetadata; +} + +// @public +export interface ModelParams extends BaseParams { + // (undocumented) + model: string; + // (undocumented) + systemInstruction?: string | Part | Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export type Part = TextPart | InlineDataPart | FunctionCallPart | FunctionResponsePart; + +// @public +export const POSSIBLE_ROLES: readonly ["user", "model", "function", "system"]; + +// @public +export interface PromptFeedback { + // (undocumented) + blockReason: BlockReason; + // (undocumented) + blockReasonMessage?: string; + // (undocumented) + safetyRatings: SafetyRating[]; +} + +// @public +export interface RequestOptions { + baseUrl?: string; + timeout?: number; +} + +// @public (undocumented) +export interface RetrievedContextAttribution { + // (undocumented) + title: string; + // (undocumented) + uri: string; +} + +// @public +export type Role = (typeof POSSIBLE_ROLES)[number]; + +// @public +export interface SafetyRating { + // (undocumented) + blocked: boolean; + // (undocumented) + category: HarmCategory; + // (undocumented) + probability: HarmProbability; + // (undocumented) + probabilityScore: number; + // (undocumented) + severity: HarmSeverity; + // (undocumented) + severityScore: number; +} + +// @public +export interface SafetySetting { + // (undocumented) + category: HarmCategory; + // (undocumented) + method: HarmBlockMethod; + // (undocumented) + threshold: HarmBlockThreshold; +} + +// @public (undocumented) +export interface Segment { + // (undocumented) + endIndex: number; + // (undocumented) + partIndex: number; + // (undocumented) + startIndex: number; +} + +// @public +export interface StartChatParams extends BaseParams { + // (undocumented) + history?: Content[]; + // (undocumented) + systemInstruction?: string | Part | Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export interface TextPart { + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData?: never; + // (undocumented) + text: string; +} + +// @public +export type Tool = FunctionDeclarationsTool; + +// @public +export interface ToolConfig { + // (undocumented) + functionCallingConfig: FunctionCallingConfig; +} + +// @public +export interface UsageMetadata { + // (undocumented) + candidatesTokenCount: number; + // (undocumented) + promptTokenCount: number; + // (undocumented) + totalTokenCount: number; +} + +// @public +export interface VertexAI { + app: FirebaseApp; + // (undocumented) + location: string; +} + +// @public +export interface VideoMetadata { + endOffset: string; + startOffset: string; +} + +// @public (undocumented) +export interface WebAttribution { + // (undocumented) + title: string; + // (undocumented) + uri: string; +} + + +``` diff --git a/packages/firebase/package.json b/packages/firebase/package.json index c2320f541c8..c1aa6405871 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -215,17 +215,17 @@ }, "default": "./storage/dist/esm/index.esm.js" }, - "./vertexai": { - "types": "./vertexai/dist/vertexai/index.d.ts", + "./vertexai-preview": { + "types": "./vertexai-preview/dist/vertexai-preview/index.d.ts", "node": { - "require": "./vertexai/dist/index.cjs.js", - "import": "./vertexai/dist/index.mjs" + "require": "./vertexai-preview/dist/index.cjs.js", + "import": "./vertexai-preview/dist/index.mjs" }, "browser": { - "require": "./vertexai/dist/index.cjs.js", - "import": "./vertexai/dist/esm/index.esm.js" + "require": "./vertexai-preview/dist/index.cjs.js", + "import": "./vertexai-preview/dist/esm/index.esm.js" }, - "default": "./vertexai/dist/esm/index.esm.js" + "default": "./vertexai-preview/dist/esm/index.esm.js" }, "./compat/analytics": { "types": "./compat/analytics/dist/compat/analytics/index.d.ts", @@ -412,7 +412,7 @@ "@firebase/app-check": "0.8.3", "@firebase/app-check-compat": "0.3.10", "@firebase/util": "1.9.5", - "@firebase/vertexai": "0.0.1" + "@firebase/vertexai-preview": "0.0.0" }, "devDependencies": { "rollup": "2.79.1", @@ -445,7 +445,7 @@ "messaging", "messaging/sw", "database", - "vertexai" + "vertexai-preview" ], "typings": "empty.d.ts" } diff --git a/packages/firebase/vertexai/index.ts b/packages/firebase/vertexai-preview/index.ts similarity index 93% rename from packages/firebase/vertexai/index.ts rename to packages/firebase/vertexai-preview/index.ts index 2645fd3004f..20d7697c1e0 100644 --- a/packages/firebase/vertexai/index.ts +++ b/packages/firebase/vertexai-preview/index.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export * from '@firebase/vertexai'; +export * from '@firebase/vertexai-preview'; diff --git a/packages/firebase/vertexai/package.json b/packages/firebase/vertexai-preview/package.json similarity index 55% rename from packages/firebase/vertexai/package.json rename to packages/firebase/vertexai-preview/package.json index 20e04a3bbb5..9dfe8f0c3fa 100644 --- a/packages/firebase/vertexai/package.json +++ b/packages/firebase/vertexai-preview/package.json @@ -1,7 +1,7 @@ { - "name": "firebase/vertexai", + "name": "firebase/vertexai-preview", "main": "dist/index.cjs.js", "browser": "dist/esm/index.esm.js", "module": "dist/esm/index.esm.js", - "typings": "dist/vertexai/index.d.ts" + "typings": "dist/vertexai-preview/index.d.ts" } \ No newline at end of file diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json index 4ae076cdda2..ce5ba1323df 100644 --- a/packages/vertexai/package.json +++ b/packages/vertexai/package.json @@ -1,7 +1,7 @@ { - "name": "@firebase/vertexai", - "version": "0.0.1", - "description": "A template package for new firebase packages", + "name": "@firebase/vertexai-preview", + "version": "0.0.0", + "description": "A Firebase SDK for VertexAI (preview)", "author": "Firebase (https://firebase.google.com/)", "engines": { "node": ">=18.0.0" @@ -30,13 +30,14 @@ "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", + "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/vertexai --include-dependencies build", "dev": "rollup -c -w", "testsetup": "yarn ts-node ./test-utils/convert-mocks.ts", "test": "run-p --npm-path npm lint test:browser", "test:ci": "yarn testsetup && node ../../scripts/run_tests_in_ci.js -s test", - "test:browser": "yarn testsetup && karma start --single-run" + "test:browser": "yarn testsetup && karma start --single-run", + "api-report": "api-extractor run --local --verbose" }, "peerDependencies": { "@firebase/app": "0.x", diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 3702a5ff17f..38409d598ba 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -48,20 +48,26 @@ export function getVertexAI( ): VertexAI { app = getModularInstance(app); // Dependencies - const vertexProvider: Provider<'vertex'> = _getProvider(app, VERTEX_TYPE); + const vertexProvider: Provider<'vertexAI'> = _getProvider(app, VERTEX_TYPE); return vertexProvider.getImmediate({ identifier: options?.location || DEFAULT_LOCATION }); } +/** + * Returns a {@link GenerativeModel} class with methods for inference + * and other functionality. + * + * @public + */ export function getGenerativeModel( - vertex: VertexAI, + vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions ): GenerativeModel { if (!modelParams.model) { throw ERROR_FACTORY.create(VertexError.NO_MODEL); } - return new GenerativeModel(vertex, modelParams, requestOptions); + return new GenerativeModel(vertexAI, modelParams, requestOptions); } diff --git a/packages/vertexai/src/constants.ts b/packages/vertexai/src/constants.ts index e4bb26baa1d..97f6d813abc 100644 --- a/packages/vertexai/src/constants.ts +++ b/packages/vertexai/src/constants.ts @@ -17,7 +17,7 @@ import { version } from '../package.json'; -export const VERTEX_TYPE = 'vertex'; +export const VERTEX_TYPE = 'vertexAI'; export const DEFAULT_LOCATION = 'us-central1'; diff --git a/packages/vertexai/src/errors.ts b/packages/vertexai/src/errors.ts index 2aa72ac44ea..c0b9d83aaeb 100644 --- a/packages/vertexai/src/errors.ts +++ b/packages/vertexai/src/errors.ts @@ -39,7 +39,7 @@ const ERRORS: ErrorMap = { `contain a valid project ID.`, [VertexError.NO_MODEL]: `Must provide a model name. ` + - `Example: genai.getGenerativeModel({ model: 'my-model-name' })`, + `Example: getGenerativeModel({ model: 'my-model-name' })`, [VertexError.PARSE_FAILED]: `Parsing failed: {$message}`, [VertexError.RESPONSE_ERROR]: `Response error: {$message}. Response body stored in ` + @@ -57,7 +57,7 @@ interface ErrorParams { } export const ERROR_FACTORY = new ErrorFactory( - 'vertex', - 'Vertex', + 'vertexAI', + 'VertexAI', ERRORS ); diff --git a/packages/vertexai/src/types/content.ts b/packages/vertexai/src/types/content.ts index 168eeda0ee9..4a1b82f4e6d 100644 --- a/packages/vertexai/src/types/content.ts +++ b/packages/vertexai/src/types/content.ts @@ -81,7 +81,7 @@ export interface VideoMetadata { } /** - * Content part interface if the part represents a {@link FunctionCall}`. + * Content part interface if the part represents a {@link FunctionCall}. * @public */ export interface FunctionCallPart { diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index 70ce881ff8a..079d72f348f 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -33,7 +33,7 @@ export interface BaseParams { } /** - * Params passed to {@link GoogleGenerativeAI.getGenerativeModel}. + * Params passed to {@link getGenerativeModel}. * @public */ export interface ModelParams extends BaseParams { diff --git a/packages/vertexai/src/types/responses.ts b/packages/vertexai/src/types/responses.ts index 375bd340e42..c46a243edef 100644 --- a/packages/vertexai/src/types/responses.ts +++ b/packages/vertexai/src/types/responses.ts @@ -74,6 +74,11 @@ export interface GenerateContentResponse { usageMetadata?: UsageMetadata; } +/** + * Usage metadata about a {@link GenerateContentResponse}. + * + * @public + */ export interface UsageMetadata { promptTokenCount: number; candidatesTokenCount: number; diff --git a/scripts/docgen/docgen.ts b/scripts/docgen/docgen.ts index 0b38da3bd8b..3f4d10ebbd9 100644 --- a/scripts/docgen/docgen.ts +++ b/scripts/docgen/docgen.ts @@ -54,7 +54,8 @@ const PREFERRED_PARAMS = [ 'messaging', 'performance', 'remoteConfig', - 'storage' + 'storage', + 'vertexAI' ]; yargs From dc2efe7b8a232cb8e90987a408af5162ad6e5ceb Mon Sep 17 00:00:00 2001 From: hsubox76 Date: Mon, 6 May 2024 20:09:06 +0000 Subject: [PATCH 23/28] Update API reports --- common/api-review/vertexai-preview.api.md | 33 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/common/api-review/vertexai-preview.api.md b/common/api-review/vertexai-preview.api.md index 0e1029bd715..96d8ec70050 100644 --- a/common/api-review/vertexai-preview.api.md +++ b/common/api-review/vertexai-preview.api.md @@ -6,6 +6,7 @@ import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; // @public export interface BaseParams { @@ -101,6 +102,28 @@ export interface EnhancedGenerateContentResponse extends GenerateContentResponse text: () => string; } +// @public +export interface FileData { + // (undocumented) + fileUri: string; + // (undocumented) + mimeType: string; +} + +// @public +export interface FileDataPart { + // (undocumented) + fileData: FileData; + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData?: never; + // (undocumented) + text?: never; +} + // @public export enum FinishReason { // (undocumented) @@ -332,7 +355,7 @@ export class GenerativeModel { export function getGenerativeModel(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions): GenerativeModel; // @public -export function getVertexAI(app?: FirebaseApp): VertexAI; +export function getVertexAI(app?: FirebaseApp, options?: VertexAIOptions): VertexAI; // @public (undocumented) export interface GroundingAttribution { @@ -448,7 +471,7 @@ export interface ModelParams extends BaseParams { } // @public -export type Part = TextPart | InlineDataPart | FunctionCallPart | FunctionResponsePart; +export type Part = TextPart | InlineDataPart | FunctionCallPart | FunctionResponsePart | FileDataPart; // @public export const POSSIBLE_ROLES: readonly ["user", "model", "function", "system"]; @@ -566,6 +589,12 @@ export interface VertexAI { location: string; } +// @public +export interface VertexAIOptions { + // (undocumented) + location?: string; +} + // @public export interface VideoMetadata { endOffset: string; From 4c83b349d25f72b9df09b4f3680fea327d599800 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 7 May 2024 11:44:41 -0700 Subject: [PATCH 24/28] fix test (#8228) --- packages/vertexai/src/requests/request-helpers.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vertexai/src/requests/request-helpers.test.ts b/packages/vertexai/src/requests/request-helpers.test.ts index d0dfbe63fc5..76b2f0ca1bf 100644 --- a/packages/vertexai/src/requests/request-helpers.test.ts +++ b/packages/vertexai/src/requests/request-helpers.test.ts @@ -186,13 +186,13 @@ describe('request formatting methods', () => { { role: 'user', parts: [ + { text: 'What is this?' }, { fileData: { mimeType: 'image/jpeg', fileUri: 'gs://sample.appspot.com/image.jpeg' } - }, - { text: 'What is this?' } + } ] } ] From f68822852cf7b38a09a9c68f55c798105b5c019d Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 7 May 2024 14:51:59 -0700 Subject: [PATCH 25/28] Generate vertex reference docs (#8226) * generate vertex reference docs * Update docs-devsite/index.md Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> * Update docs-devsite/vertexai-preview.vertexaioptions.md Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> * addressed PR comments * regenerate --------- Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> --- docs-devsite/index.md | 1 + docs-devsite/vertexai-preview.baseparams.md | 42 ++ docs-devsite/vertexai-preview.chatsession.md | 138 +++++++ docs-devsite/vertexai-preview.citation.md | 78 ++++ .../vertexai-preview.citationmetadata.md | 33 ++ docs-devsite/vertexai-preview.content.md | 42 ++ .../vertexai-preview.counttokensrequest.md | 33 ++ .../vertexai-preview.counttokensresponse.md | 46 +++ docs-devsite/vertexai-preview.date_2.md | 51 +++ ...preview.enhancedgeneratecontentresponse.md | 45 +++ docs-devsite/vertexai-preview.filedata.md | 42 ++ docs-devsite/vertexai-preview.filedatapart.md | 69 ++++ docs-devsite/vertexai-preview.functioncall.md | 42 ++ .../vertexai-preview.functioncallingconfig.md | 41 ++ .../vertexai-preview.functioncallpart.md | 60 +++ .../vertexai-preview.functiondeclaration.md | 57 +++ ...texai-preview.functiondeclarationschema.md | 70 ++++ ...eview.functiondeclarationschemaproperty.md | 125 ++++++ ...rtexai-preview.functiondeclarationstool.md | 35 ++ .../vertexai-preview.functionresponse.md | 42 ++ .../vertexai-preview.functionresponsepart.md | 60 +++ ...rtexai-preview.generatecontentcandidate.md | 87 +++++ ...vertexai-preview.generatecontentrequest.md | 61 +++ ...ertexai-preview.generatecontentresponse.md | 51 +++ .../vertexai-preview.generatecontentresult.md | 33 ++ ...xai-preview.generatecontentstreamresult.md | 42 ++ .../vertexai-preview.generationconfig.md | 96 +++++ .../vertexai-preview.generativecontentblob.md | 44 +++ .../vertexai-preview.generativemodel.md | 201 ++++++++++ .../vertexai-preview.groundingattribution.md | 59 +++ .../vertexai-preview.groundingmetadata.md | 51 +++ .../vertexai-preview.inlinedatapart.md | 71 ++++ docs-devsite/vertexai-preview.md | 369 ++++++++++++++++++ docs-devsite/vertexai-preview.modelparams.md | 61 +++ .../vertexai-preview.promptfeedback.md | 51 +++ .../vertexai-preview.requestoptions.md | 46 +++ ...xai-preview.retrievedcontextattribution.md | 41 ++ docs-devsite/vertexai-preview.safetyrating.md | 78 ++++ .../vertexai-preview.safetysetting.md | 51 +++ docs-devsite/vertexai-preview.segment.md | 50 +++ .../vertexai-preview.startchatparams.md | 61 +++ docs-devsite/vertexai-preview.textpart.md | 60 +++ docs-devsite/vertexai-preview.toolconfig.md | 33 ++ .../vertexai-preview.usagemetadata.md | 51 +++ docs-devsite/vertexai-preview.vertexai.md | 44 +++ .../vertexai-preview.vertexaioptions.md | 33 ++ .../vertexai-preview.videometadata.md | 46 +++ .../vertexai-preview.webattribution.md | 41 ++ packages/vertexai/src/public-types.ts | 2 +- packages/vertexai/src/types/content.ts | 13 +- packages/vertexai/src/types/requests.ts | 9 +- packages/vertexai/src/types/responses.ts | 4 +- 52 files changed, 3079 insertions(+), 13 deletions(-) create mode 100644 docs-devsite/vertexai-preview.baseparams.md create mode 100644 docs-devsite/vertexai-preview.chatsession.md create mode 100644 docs-devsite/vertexai-preview.citation.md create mode 100644 docs-devsite/vertexai-preview.citationmetadata.md create mode 100644 docs-devsite/vertexai-preview.content.md create mode 100644 docs-devsite/vertexai-preview.counttokensrequest.md create mode 100644 docs-devsite/vertexai-preview.counttokensresponse.md create mode 100644 docs-devsite/vertexai-preview.date_2.md create mode 100644 docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md create mode 100644 docs-devsite/vertexai-preview.filedata.md create mode 100644 docs-devsite/vertexai-preview.filedatapart.md create mode 100644 docs-devsite/vertexai-preview.functioncall.md create mode 100644 docs-devsite/vertexai-preview.functioncallingconfig.md create mode 100644 docs-devsite/vertexai-preview.functioncallpart.md create mode 100644 docs-devsite/vertexai-preview.functiondeclaration.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationschema.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationstool.md create mode 100644 docs-devsite/vertexai-preview.functionresponse.md create mode 100644 docs-devsite/vertexai-preview.functionresponsepart.md create mode 100644 docs-devsite/vertexai-preview.generatecontentcandidate.md create mode 100644 docs-devsite/vertexai-preview.generatecontentrequest.md create mode 100644 docs-devsite/vertexai-preview.generatecontentresponse.md create mode 100644 docs-devsite/vertexai-preview.generatecontentresult.md create mode 100644 docs-devsite/vertexai-preview.generatecontentstreamresult.md create mode 100644 docs-devsite/vertexai-preview.generationconfig.md create mode 100644 docs-devsite/vertexai-preview.generativecontentblob.md create mode 100644 docs-devsite/vertexai-preview.generativemodel.md create mode 100644 docs-devsite/vertexai-preview.groundingattribution.md create mode 100644 docs-devsite/vertexai-preview.groundingmetadata.md create mode 100644 docs-devsite/vertexai-preview.inlinedatapart.md create mode 100644 docs-devsite/vertexai-preview.md create mode 100644 docs-devsite/vertexai-preview.modelparams.md create mode 100644 docs-devsite/vertexai-preview.promptfeedback.md create mode 100644 docs-devsite/vertexai-preview.requestoptions.md create mode 100644 docs-devsite/vertexai-preview.retrievedcontextattribution.md create mode 100644 docs-devsite/vertexai-preview.safetyrating.md create mode 100644 docs-devsite/vertexai-preview.safetysetting.md create mode 100644 docs-devsite/vertexai-preview.segment.md create mode 100644 docs-devsite/vertexai-preview.startchatparams.md create mode 100644 docs-devsite/vertexai-preview.textpart.md create mode 100644 docs-devsite/vertexai-preview.toolconfig.md create mode 100644 docs-devsite/vertexai-preview.usagemetadata.md create mode 100644 docs-devsite/vertexai-preview.vertexai.md create mode 100644 docs-devsite/vertexai-preview.vertexaioptions.md create mode 100644 docs-devsite/vertexai-preview.videometadata.md create mode 100644 docs-devsite/vertexai-preview.webattribution.md diff --git a/docs-devsite/index.md b/docs-devsite/index.md index 6a32ccdbaa6..d777885ef1a 100644 --- a/docs-devsite/index.md +++ b/docs-devsite/index.md @@ -27,4 +27,5 @@ https://github.com/firebase/firebase-js-sdk | [@firebase/performance](./performance.md#performance_package) | The Firebase Performance Monitoring Web SDK. This SDK does not work in a Node.js environment. | | [@firebase/remote-config](./remote-config.md#remote-config_package) | The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environment. | | [@firebase/storage](./storage.md#storage_package) | Cloud Storage for Firebase | +| [@firebase/vertexai-preview](./vertexai-preview.md#vertexai-preview_package) | The Firebase Vertex Web SDK. | diff --git a/docs-devsite/vertexai-preview.baseparams.md b/docs-devsite/vertexai-preview.baseparams.md new file mode 100644 index 00000000000..6756c919ccc --- /dev/null +++ b/docs-devsite/vertexai-preview.baseparams.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# BaseParams interface +Base parameters for a number of methods. + +Signature: + +```typescript +export interface BaseParams +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [generationConfig](./vertexai-preview.baseparams.md#baseparamsgenerationconfig) | [GenerationConfig](./vertexai-preview.generationconfig.md#generationconfig_interface) | | +| [safetySettings](./vertexai-preview.baseparams.md#baseparamssafetysettings) | [SafetySetting](./vertexai-preview.safetysetting.md#safetysetting_interface)\[\] | | + +## BaseParams.generationConfig + +Signature: + +```typescript +generationConfig?: GenerationConfig; +``` + +## BaseParams.safetySettings + +Signature: + +```typescript +safetySettings?: SafetySetting[]; +``` diff --git a/docs-devsite/vertexai-preview.chatsession.md b/docs-devsite/vertexai-preview.chatsession.md new file mode 100644 index 00000000000..3d78bab3745 --- /dev/null +++ b/docs-devsite/vertexai-preview.chatsession.md @@ -0,0 +1,138 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ChatSession class +ChatSession class that enables sending chat messages and stores history of sent and received messages so far. + +Signature: + +```typescript +export declare class ChatSession +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(apiSettings, model, params, requestOptions)](./vertexai-preview.chatsession.md#chatsessionconstructor) | | Constructs a new instance of the ChatSession class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [model](./vertexai-preview.chatsession.md#chatsessionmodel) | | string | | +| [params](./vertexai-preview.chatsession.md#chatsessionparams) | | [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) \| undefined | | +| [requestOptions](./vertexai-preview.chatsession.md#chatsessionrequestoptions) | | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) \| undefined | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [getHistory()](./vertexai-preview.chatsession.md#chatsessiongethistory) | | Gets the chat history so far. Blocked prompts are not added to history. Blocked candidates are not added to history, nor are the prompts that generated them. | +| [sendMessage(request)](./vertexai-preview.chatsession.md#chatsessionsendmessage) | | Sends a chat message and receives a non-streaming [GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface) | +| [sendMessageStream(request)](./vertexai-preview.chatsession.md#chatsessionsendmessagestream) | | Sends a chat message and receives the response as a [GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface) containing an iterable stream and a response promise. | + +## ChatSession.(constructor) + +Constructs a new instance of the `ChatSession` class + +Signature: + +```typescript +constructor(apiSettings: ApiSettings, model: string, params?: StartChatParams | undefined, requestOptions?: RequestOptions | undefined); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| apiSettings | ApiSettings | | +| model | string | | +| params | [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) \| undefined | | +| requestOptions | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) \| undefined | | + +## ChatSession.model + +Signature: + +```typescript +model: string; +``` + +## ChatSession.params + +Signature: + +```typescript +params?: StartChatParams | undefined; +``` + +## ChatSession.requestOptions + +Signature: + +```typescript +requestOptions?: RequestOptions | undefined; +``` + +## ChatSession.getHistory() + +Gets the chat history so far. Blocked prompts are not added to history. Blocked candidates are not added to history, nor are the prompts that generated them. + +Signature: + +```typescript +getHistory(): Promise; +``` +Returns: + +Promise<[Content](./vertexai-preview.content.md#content_interface)\[\]> + +## ChatSession.sendMessage() + +Sends a chat message and receives a non-streaming [GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface) + +Signature: + +```typescript +sendMessage(request: string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface)> + +## ChatSession.sendMessageStream() + +Sends a chat message and receives the response as a [GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface) containing an iterable stream and a response promise. + +Signature: + +```typescript +sendMessageStream(request: string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface)> + diff --git a/docs-devsite/vertexai-preview.citation.md b/docs-devsite/vertexai-preview.citation.md new file mode 100644 index 00000000000..10a615ee247 --- /dev/null +++ b/docs-devsite/vertexai-preview.citation.md @@ -0,0 +1,78 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Citation interface +A single citation. + +Signature: + +```typescript +export interface Citation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [endIndex](./vertexai-preview.citation.md#citationendindex) | number | | +| [license](./vertexai-preview.citation.md#citationlicense) | string | | +| [publicationDate](./vertexai-preview.citation.md#citationpublicationdate) | Date | | +| [startIndex](./vertexai-preview.citation.md#citationstartindex) | number | | +| [title](./vertexai-preview.citation.md#citationtitle) | string | | +| [uri](./vertexai-preview.citation.md#citationuri) | string | | + +## Citation.endIndex + +Signature: + +```typescript +endIndex?: number; +``` + +## Citation.license + +Signature: + +```typescript +license?: string; +``` + +## Citation.publicationDate + +Signature: + +```typescript +publicationDate?: Date; +``` + +## Citation.startIndex + +Signature: + +```typescript +startIndex?: number; +``` + +## Citation.title + +Signature: + +```typescript +title?: string; +``` + +## Citation.uri + +Signature: + +```typescript +uri?: string; +``` diff --git a/docs-devsite/vertexai-preview.citationmetadata.md b/docs-devsite/vertexai-preview.citationmetadata.md new file mode 100644 index 00000000000..fa740ca1af7 --- /dev/null +++ b/docs-devsite/vertexai-preview.citationmetadata.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CitationMetadata interface +Citation metadata that may be found on a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface). + +Signature: + +```typescript +export interface CitationMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [citations](./vertexai-preview.citationmetadata.md#citationmetadatacitations) | [Citation](./vertexai-preview.citation.md#citation_interface)\[\] | | + +## CitationMetadata.citations + +Signature: + +```typescript +citations: Citation[]; +``` diff --git a/docs-devsite/vertexai-preview.content.md b/docs-devsite/vertexai-preview.content.md new file mode 100644 index 00000000000..26198a3951f --- /dev/null +++ b/docs-devsite/vertexai-preview.content.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Content interface +Content type for both prompts and response candidates. + +Signature: + +```typescript +export interface Content +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [parts](./vertexai-preview.content.md#contentparts) | [Part](./vertexai-preview.md#part)\[\] | | +| [role](./vertexai-preview.content.md#contentrole) | [Role](./vertexai-preview.md#role) | | + +## Content.parts + +Signature: + +```typescript +parts: Part[]; +``` + +## Content.role + +Signature: + +```typescript +role: Role; +``` diff --git a/docs-devsite/vertexai-preview.counttokensrequest.md b/docs-devsite/vertexai-preview.counttokensrequest.md new file mode 100644 index 00000000000..07e5f0d85f3 --- /dev/null +++ b/docs-devsite/vertexai-preview.counttokensrequest.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CountTokensRequest interface +Params for calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens) + +Signature: + +```typescript +export interface CountTokensRequest +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [contents](./vertexai-preview.counttokensrequest.md#counttokensrequestcontents) | [Content](./vertexai-preview.content.md#content_interface)\[\] | | + +## CountTokensRequest.contents + +Signature: + +```typescript +contents: Content[]; +``` diff --git a/docs-devsite/vertexai-preview.counttokensresponse.md b/docs-devsite/vertexai-preview.counttokensresponse.md new file mode 100644 index 00000000000..d097d5aacff --- /dev/null +++ b/docs-devsite/vertexai-preview.counttokensresponse.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CountTokensResponse interface +Response from calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens). + +Signature: + +```typescript +export interface CountTokensResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [totalBillableCharacters](./vertexai-preview.counttokensresponse.md#counttokensresponsetotalbillablecharacters) | number | The total number of billable characters counted across all instances from the request. | +| [totalTokens](./vertexai-preview.counttokensresponse.md#counttokensresponsetotaltokens) | number | The total number of tokens counted across all instances from the request. | + +## CountTokensResponse.totalBillableCharacters + +The total number of billable characters counted across all instances from the request. + +Signature: + +```typescript +totalBillableCharacters?: number; +``` + +## CountTokensResponse.totalTokens + +The total number of tokens counted across all instances from the request. + +Signature: + +```typescript +totalTokens: number; +``` diff --git a/docs-devsite/vertexai-preview.date_2.md b/docs-devsite/vertexai-preview.date_2.md new file mode 100644 index 00000000000..5af031447c4 --- /dev/null +++ b/docs-devsite/vertexai-preview.date_2.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Date_2 interface +Protobuf google.type.Date + +Signature: + +```typescript +export interface Date +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [day](./vertexai-preview.date_2.md#date_2day) | number | | +| [month](./vertexai-preview.date_2.md#date_2month) | number | | +| [year](./vertexai-preview.date_2.md#date_2year) | number | | + +## Date\_2.day + +Signature: + +```typescript +day: number; +``` + +## Date\_2.month + +Signature: + +```typescript +month: number; +``` + +## Date\_2.year + +Signature: + +```typescript +year: number; +``` diff --git a/docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md b/docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md new file mode 100644 index 00000000000..132c5ed0be2 --- /dev/null +++ b/docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md @@ -0,0 +1,45 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# EnhancedGenerateContentResponse interface +Response object wrapped with helper methods. + +Signature: + +```typescript +export interface EnhancedGenerateContentResponse extends GenerateContentResponse +``` +Extends: [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCalls](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface)\[\] \| undefined | | +| [text](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsetext) | () => string | Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. | + +## EnhancedGenerateContentResponse.functionCalls + +Signature: + +```typescript +functionCalls: () => FunctionCall[] | undefined; +``` + +## EnhancedGenerateContentResponse.text + +Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. + +Signature: + +```typescript +text: () => string; +``` diff --git a/docs-devsite/vertexai-preview.filedata.md b/docs-devsite/vertexai-preview.filedata.md new file mode 100644 index 00000000000..577b4b1910d --- /dev/null +++ b/docs-devsite/vertexai-preview.filedata.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FileData interface +Data pointing to a file uploaded on Google Cloud Storage. + +Signature: + +```typescript +export interface FileData +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [fileUri](./vertexai-preview.filedata.md#filedatafileuri) | string | | +| [mimeType](./vertexai-preview.filedata.md#filedatamimetype) | string | | + +## FileData.fileUri + +Signature: + +```typescript +fileUri: string; +``` + +## FileData.mimeType + +Signature: + +```typescript +mimeType: string; +``` diff --git a/docs-devsite/vertexai-preview.filedatapart.md b/docs-devsite/vertexai-preview.filedatapart.md new file mode 100644 index 00000000000..e03c056f588 --- /dev/null +++ b/docs-devsite/vertexai-preview.filedatapart.md @@ -0,0 +1,69 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FileDataPart interface +Content part interface if the part represents [FileData](./vertexai-preview.filedata.md#filedata_interface) + +Signature: + +```typescript +export interface FileDataPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [fileData](./vertexai-preview.filedatapart.md#filedatapartfiledata) | [FileData](./vertexai-preview.filedata.md#filedata_interface) | | +| [functionCall](./vertexai-preview.filedatapart.md#filedatapartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.filedatapart.md#filedatapartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.filedatapart.md#filedatapartinlinedata) | never | | +| [text](./vertexai-preview.filedatapart.md#filedataparttext) | never | | + +## FileDataPart.fileData + +Signature: + +```typescript +fileData: FileData; +``` + +## FileDataPart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## FileDataPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## FileDataPart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## FileDataPart.text + +Signature: + +```typescript +text?: never; +``` diff --git a/docs-devsite/vertexai-preview.functioncall.md b/docs-devsite/vertexai-preview.functioncall.md new file mode 100644 index 00000000000..60e0ea50dc4 --- /dev/null +++ b/docs-devsite/vertexai-preview.functioncall.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionCall interface +A predicted [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) returned from the model that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing the parameters and their values. + +Signature: + +```typescript +export interface FunctionCall +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [args](./vertexai-preview.functioncall.md#functioncallargs) | object | | +| [name](./vertexai-preview.functioncall.md#functioncallname) | string | | + +## FunctionCall.args + +Signature: + +```typescript +args: object; +``` + +## FunctionCall.name + +Signature: + +```typescript +name: string; +``` diff --git a/docs-devsite/vertexai-preview.functioncallingconfig.md b/docs-devsite/vertexai-preview.functioncallingconfig.md new file mode 100644 index 00000000000..1965fb76e95 --- /dev/null +++ b/docs-devsite/vertexai-preview.functioncallingconfig.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionCallingConfig interface + +Signature: + +```typescript +export interface FunctionCallingConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [allowedFunctionNames](./vertexai-preview.functioncallingconfig.md#functioncallingconfigallowedfunctionnames) | string\[\] | | +| [mode](./vertexai-preview.functioncallingconfig.md#functioncallingconfigmode) | [FunctionCallingMode](./vertexai-preview.md#functioncallingmode) | | + +## FunctionCallingConfig.allowedFunctionNames + +Signature: + +```typescript +allowedFunctionNames?: string[]; +``` + +## FunctionCallingConfig.mode + +Signature: + +```typescript +mode?: FunctionCallingMode; +``` diff --git a/docs-devsite/vertexai-preview.functioncallpart.md b/docs-devsite/vertexai-preview.functioncallpart.md new file mode 100644 index 00000000000..5da204692f9 --- /dev/null +++ b/docs-devsite/vertexai-preview.functioncallpart.md @@ -0,0 +1,60 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionCallPart interface +Content part interface if the part represents a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface). + +Signature: + +```typescript +export interface FunctionCallPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.functioncallpart.md#functioncallpartfunctioncall) | [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) | | +| [functionResponse](./vertexai-preview.functioncallpart.md#functioncallpartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.functioncallpart.md#functioncallpartinlinedata) | never | | +| [text](./vertexai-preview.functioncallpart.md#functioncallparttext) | never | | + +## FunctionCallPart.functionCall + +Signature: + +```typescript +functionCall: FunctionCall; +``` + +## FunctionCallPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## FunctionCallPart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## FunctionCallPart.text + +Signature: + +```typescript +text?: never; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclaration.md b/docs-devsite/vertexai-preview.functiondeclaration.md new file mode 100644 index 00000000000..e725b9557e1 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclaration.md @@ -0,0 +1,57 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclaration interface +Structured representation of a function declaration as defined by the [OpenAPI 3.0 specification](https://spec.openapis.org/oas/v3.0.3). Included in this declaration are the function name and parameters. This `FunctionDeclaration` is a representation of a block of code that can be used as a Tool by the model and executed by the client. + +Signature: + +```typescript +export declare interface FunctionDeclaration +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./vertexai-preview.functiondeclaration.md#functiondeclarationdescription) | string | Optional. Description and purpose of the function. Model uses it to decide how and whether to call the function. | +| [name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) | string | The name of the function to call. Must start with a letter or an underscore. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a max length of 64. | +| [parameters](./vertexai-preview.functiondeclaration.md#functiondeclarationparameters) | [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | Optional. Describes the parameters to this function in JSON Schema Object format. Reflects the Open API 3.03 Parameter Object. Parameter names are case sensitive. For a function with no parameters, this can be left unset. | + +## FunctionDeclaration.description + +Optional. Description and purpose of the function. Model uses it to decide how and whether to call the function. + +Signature: + +```typescript +description?: string; +``` + +## FunctionDeclaration.name + +The name of the function to call. Must start with a letter or an underscore. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a max length of 64. + +Signature: + +```typescript +name: string; +``` + +## FunctionDeclaration.parameters + +Optional. Describes the parameters to this function in JSON Schema Object format. Reflects the Open API 3.03 Parameter Object. Parameter names are case sensitive. For a function with no parameters, this can be left unset. + +Signature: + +```typescript +parameters?: FunctionDeclarationSchema; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclarationschema.md b/docs-devsite/vertexai-preview.functiondeclarationschema.md new file mode 100644 index 00000000000..7d0e5809d41 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclarationschema.md @@ -0,0 +1,70 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclarationSchema interface +Schema for parameters passed to [FunctionDeclaration.parameters](./vertexai-preview.functiondeclaration.md#functiondeclarationparameters). + +Signature: + +```typescript +export interface FunctionDeclarationSchema +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschemadescription) | string | Optional. Description of the parameter. | +| [properties](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschemaproperties) | { \[k: string\]: [FunctionDeclarationSchemaProperty](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemaproperty_interface); } | The format of the parameter. | +| [required](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschemarequired) | string\[\] | Optional. Array of required parameters. | +| [type](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschematype) | [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype) | The type of the parameter. | + +## FunctionDeclarationSchema.description + +Optional. Description of the parameter. + +Signature: + +```typescript +description?: string; +``` + +## FunctionDeclarationSchema.properties + +The format of the parameter. + +Signature: + +```typescript +properties: { + [k: string]: FunctionDeclarationSchemaProperty; + }; +``` + +## FunctionDeclarationSchema.required + +Optional. Array of required parameters. + +Signature: + +```typescript +required?: string[]; +``` + +## FunctionDeclarationSchema.type + +The type of the parameter. + +Signature: + +```typescript +type: FunctionDeclarationSchemaType; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md b/docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md new file mode 100644 index 00000000000..ac2e1262dd0 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md @@ -0,0 +1,125 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclarationSchemaProperty interface +Schema is used to define the format of input/output data. Represents a select subset of an OpenAPI 3.0 schema object. More fields may be added in the future as needed. + +Signature: + +```typescript +export interface FunctionDeclarationSchemaProperty +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertydescription) | string | Optional. The description of the property. | +| [enum](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyenum) | string\[\] | Optional. The enum of the property. | +| [example](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyexample) | unknown | Optional. The example of the property. | +| [format](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyformat) | string | Optional. The format of the property. | +| [items](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyitems) | [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | Optional. The items of the property. [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | +| [nullable](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertynullable) | boolean | Optional. Whether the property is nullable. | +| [properties](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyproperties) | { \[k: string\]: [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface); } | Optional. Map of [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface). | +| [required](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyrequired) | string\[\] | Optional. Array of required property. | +| [type](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertytype) | [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype) | Optional. The type of the property. [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype). | + +## FunctionDeclarationSchemaProperty.description + +Optional. The description of the property. + +Signature: + +```typescript +description?: string; +``` + +## FunctionDeclarationSchemaProperty.enum + +Optional. The enum of the property. + +Signature: + +```typescript +enum?: string[]; +``` + +## FunctionDeclarationSchemaProperty.example + +Optional. The example of the property. + +Signature: + +```typescript +example?: unknown; +``` + +## FunctionDeclarationSchemaProperty.format + +Optional. The format of the property. + +Signature: + +```typescript +format?: string; +``` + +## FunctionDeclarationSchemaProperty.items + +Optional. The items of the property. [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) + +Signature: + +```typescript +items?: FunctionDeclarationSchema; +``` + +## FunctionDeclarationSchemaProperty.nullable + +Optional. Whether the property is nullable. + +Signature: + +```typescript +nullable?: boolean; +``` + +## FunctionDeclarationSchemaProperty.properties + +Optional. Map of [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface). + +Signature: + +```typescript +properties?: { + [k: string]: FunctionDeclarationSchema; + }; +``` + +## FunctionDeclarationSchemaProperty.required + +Optional. Array of required property. + +Signature: + +```typescript +required?: string[]; +``` + +## FunctionDeclarationSchemaProperty.type + +Optional. The type of the property. [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype). + +Signature: + +```typescript +type?: FunctionDeclarationSchemaType; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclarationstool.md b/docs-devsite/vertexai-preview.functiondeclarationstool.md new file mode 100644 index 00000000000..d1af4351a23 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclarationstool.md @@ -0,0 +1,35 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclarationsTool interface +A `FunctionDeclarationsTool` is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. + +Signature: + +```typescript +export declare interface FunctionDeclarationsTool +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionDeclarations](./vertexai-preview.functiondeclarationstool.md#functiondeclarationstoolfunctiondeclarations) | [FunctionDeclaration](./vertexai-preview.functiondeclaration.md#functiondeclaration_interface)\[\] | Optional. One or more function declarations to be passed to the model along with the current user query. Model may decide to call a subset of these functions by populating [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) in the response. User should provide a [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) for each function call in the next turn. Based on the function responses, the model will generate the final response back to the user. Maximum 64 function declarations can be provided. | + +## FunctionDeclarationsTool.functionDeclarations + +Optional. One or more function declarations to be passed to the model along with the current user query. Model may decide to call a subset of these functions by populating [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) in the response. User should provide a [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) for each function call in the next turn. Based on the function responses, the model will generate the final response back to the user. Maximum 64 function declarations can be provided. + +Signature: + +```typescript +functionDeclarations?: FunctionDeclaration[]; +``` diff --git a/docs-devsite/vertexai-preview.functionresponse.md b/docs-devsite/vertexai-preview.functionresponse.md new file mode 100644 index 00000000000..d89ace08df1 --- /dev/null +++ b/docs-devsite/vertexai-preview.functionresponse.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionResponse interface +The result output from a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing any output from the function is used as context to the model. This should contain the result of a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) made based on model prediction. + +Signature: + +```typescript +export interface FunctionResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [name](./vertexai-preview.functionresponse.md#functionresponsename) | string | | +| [response](./vertexai-preview.functionresponse.md#functionresponseresponse) | object | | + +## FunctionResponse.name + +Signature: + +```typescript +name: string; +``` + +## FunctionResponse.response + +Signature: + +```typescript +response: object; +``` diff --git a/docs-devsite/vertexai-preview.functionresponsepart.md b/docs-devsite/vertexai-preview.functionresponsepart.md new file mode 100644 index 00000000000..4e246d625f6 --- /dev/null +++ b/docs-devsite/vertexai-preview.functionresponsepart.md @@ -0,0 +1,60 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionResponsePart interface +Content part interface if the part represents [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface). + +Signature: + +```typescript +export interface FunctionResponsePart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.functionresponsepart.md#functionresponsepartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.functionresponsepart.md#functionresponsepartfunctionresponse) | [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) | | +| [inlineData](./vertexai-preview.functionresponsepart.md#functionresponsepartinlinedata) | never | | +| [text](./vertexai-preview.functionresponsepart.md#functionresponseparttext) | never | | + +## FunctionResponsePart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## FunctionResponsePart.functionResponse + +Signature: + +```typescript +functionResponse: FunctionResponse; +``` + +## FunctionResponsePart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## FunctionResponsePart.text + +Signature: + +```typescript +text?: never; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentcandidate.md b/docs-devsite/vertexai-preview.generatecontentcandidate.md new file mode 100644 index 00000000000..9f36fab1e87 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentcandidate.md @@ -0,0 +1,87 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentCandidate interface +A candidate returned as part of a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). + +Signature: + +```typescript +export interface GenerateContentCandidate +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [citationMetadata](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatecitationmetadata) | [CitationMetadata](./vertexai-preview.citationmetadata.md#citationmetadata_interface) | | +| [content](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatecontent) | [Content](./vertexai-preview.content.md#content_interface) | | +| [finishMessage](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatefinishmessage) | string | | +| [finishReason](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatefinishreason) | [FinishReason](./vertexai-preview.md#finishreason) | | +| [groundingMetadata](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidategroundingmetadata) | [GroundingMetadata](./vertexai-preview.groundingmetadata.md#groundingmetadata_interface) | | +| [index](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidateindex) | number | | +| [safetyRatings](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatesafetyratings) | [SafetyRating](./vertexai-preview.safetyrating.md#safetyrating_interface)\[\] | | + +## GenerateContentCandidate.citationMetadata + +Signature: + +```typescript +citationMetadata?: CitationMetadata; +``` + +## GenerateContentCandidate.content + +Signature: + +```typescript +content: Content; +``` + +## GenerateContentCandidate.finishMessage + +Signature: + +```typescript +finishMessage?: string; +``` + +## GenerateContentCandidate.finishReason + +Signature: + +```typescript +finishReason?: FinishReason; +``` + +## GenerateContentCandidate.groundingMetadata + +Signature: + +```typescript +groundingMetadata?: GroundingMetadata; +``` + +## GenerateContentCandidate.index + +Signature: + +```typescript +index: number; +``` + +## GenerateContentCandidate.safetyRatings + +Signature: + +```typescript +safetyRatings?: SafetyRating[]; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentrequest.md b/docs-devsite/vertexai-preview.generatecontentrequest.md new file mode 100644 index 00000000000..68ce52340e8 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentrequest.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentRequest interface +Request sent through [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) + +Signature: + +```typescript +export interface GenerateContentRequest extends BaseParams +``` +Extends: [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [contents](./vertexai-preview.generatecontentrequest.md#generatecontentrequestcontents) | [Content](./vertexai-preview.content.md#content_interface)\[\] | | +| [systemInstruction](./vertexai-preview.generatecontentrequest.md#generatecontentrequestsysteminstruction) | string \| [Part](./vertexai-preview.md#part) \| [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.generatecontentrequest.md#generatecontentrequesttoolconfig) | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.generatecontentrequest.md#generatecontentrequesttools) | [Tool](./vertexai-preview.md#tool)\[\] | | + +## GenerateContentRequest.contents + +Signature: + +```typescript +contents: Content[]; +``` + +## GenerateContentRequest.systemInstruction + +Signature: + +```typescript +systemInstruction?: string | Part | Content; +``` + +## GenerateContentRequest.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## GenerateContentRequest.tools + +Signature: + +```typescript +tools?: Tool[]; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentresponse.md b/docs-devsite/vertexai-preview.generatecontentresponse.md new file mode 100644 index 00000000000..cb0fb0e3209 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentresponse.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentResponse interface +Individual response from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) and [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream). `generateContentStream()` will return one in each chunk until the stream is done. + +Signature: + +```typescript +export interface GenerateContentResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [candidates](./vertexai-preview.generatecontentresponse.md#generatecontentresponsecandidates) | [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface)\[\] | | +| [promptFeedback](./vertexai-preview.generatecontentresponse.md#generatecontentresponsepromptfeedback) | [PromptFeedback](./vertexai-preview.promptfeedback.md#promptfeedback_interface) | | +| [usageMetadata](./vertexai-preview.generatecontentresponse.md#generatecontentresponseusagemetadata) | [UsageMetadata](./vertexai-preview.usagemetadata.md#usagemetadata_interface) | | + +## GenerateContentResponse.candidates + +Signature: + +```typescript +candidates?: GenerateContentCandidate[]; +``` + +## GenerateContentResponse.promptFeedback + +Signature: + +```typescript +promptFeedback?: PromptFeedback; +``` + +## GenerateContentResponse.usageMetadata + +Signature: + +```typescript +usageMetadata?: UsageMetadata; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentresult.md b/docs-devsite/vertexai-preview.generatecontentresult.md new file mode 100644 index 00000000000..87249a5bc55 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentresult.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentResult interface +Result object returned from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) call. + +Signature: + +```typescript +export interface GenerateContentResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [response](./vertexai-preview.generatecontentresult.md#generatecontentresultresponse) | [EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface) | | + +## GenerateContentResult.response + +Signature: + +```typescript +response: EnhancedGenerateContentResponse; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentstreamresult.md b/docs-devsite/vertexai-preview.generatecontentstreamresult.md new file mode 100644 index 00000000000..6fd46600079 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentstreamresult.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentStreamResult interface +Result object returned from [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream) call. Iterate over `stream` to get chunks as they come in and/or use the `response` promise to get the aggregated response when the stream is done. + +Signature: + +```typescript +export interface GenerateContentStreamResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [response](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresultresponse) | Promise<[EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface)> | | +| [stream](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresultstream) | AsyncGenerator<[EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface)> | | + +## GenerateContentStreamResult.response + +Signature: + +```typescript +response: Promise; +``` + +## GenerateContentStreamResult.stream + +Signature: + +```typescript +stream: AsyncGenerator; +``` diff --git a/docs-devsite/vertexai-preview.generationconfig.md b/docs-devsite/vertexai-preview.generationconfig.md new file mode 100644 index 00000000000..0aa8e5567f7 --- /dev/null +++ b/docs-devsite/vertexai-preview.generationconfig.md @@ -0,0 +1,96 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerationConfig interface +Config options for content-related requests + +Signature: + +```typescript +export interface GenerationConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [candidateCount](./vertexai-preview.generationconfig.md#generationconfigcandidatecount) | number | | +| [frequencyPenalty](./vertexai-preview.generationconfig.md#generationconfigfrequencypenalty) | number | | +| [maxOutputTokens](./vertexai-preview.generationconfig.md#generationconfigmaxoutputtokens) | number | | +| [presencePenalty](./vertexai-preview.generationconfig.md#generationconfigpresencepenalty) | number | | +| [stopSequences](./vertexai-preview.generationconfig.md#generationconfigstopsequences) | string\[\] | | +| [temperature](./vertexai-preview.generationconfig.md#generationconfigtemperature) | number | | +| [topK](./vertexai-preview.generationconfig.md#generationconfigtopk) | number | | +| [topP](./vertexai-preview.generationconfig.md#generationconfigtopp) | number | | + +## GenerationConfig.candidateCount + +Signature: + +```typescript +candidateCount?: number; +``` + +## GenerationConfig.frequencyPenalty + +Signature: + +```typescript +frequencyPenalty?: number; +``` + +## GenerationConfig.maxOutputTokens + +Signature: + +```typescript +maxOutputTokens?: number; +``` + +## GenerationConfig.presencePenalty + +Signature: + +```typescript +presencePenalty?: number; +``` + +## GenerationConfig.stopSequences + +Signature: + +```typescript +stopSequences?: string[]; +``` + +## GenerationConfig.temperature + +Signature: + +```typescript +temperature?: number; +``` + +## GenerationConfig.topK + +Signature: + +```typescript +topK?: number; +``` + +## GenerationConfig.topP + +Signature: + +```typescript +topP?: number; +``` diff --git a/docs-devsite/vertexai-preview.generativecontentblob.md b/docs-devsite/vertexai-preview.generativecontentblob.md new file mode 100644 index 00000000000..b5dcb272027 --- /dev/null +++ b/docs-devsite/vertexai-preview.generativecontentblob.md @@ -0,0 +1,44 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerativeContentBlob interface +Interface for sending an image. + +Signature: + +```typescript +export interface GenerativeContentBlob +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [data](./vertexai-preview.generativecontentblob.md#generativecontentblobdata) | string | Image as a base64 string. | +| [mimeType](./vertexai-preview.generativecontentblob.md#generativecontentblobmimetype) | string | | + +## GenerativeContentBlob.data + +Image as a base64 string. + +Signature: + +```typescript +data: string; +``` + +## GenerativeContentBlob.mimeType + +Signature: + +```typescript +mimeType: string; +``` diff --git a/docs-devsite/vertexai-preview.generativemodel.md b/docs-devsite/vertexai-preview.generativemodel.md new file mode 100644 index 00000000000..a50fac631cc --- /dev/null +++ b/docs-devsite/vertexai-preview.generativemodel.md @@ -0,0 +1,201 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerativeModel class +Class for generative model APIs. + +Signature: + +```typescript +export declare class GenerativeModel +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(vertexAI, modelParams, requestOptions)](./vertexai-preview.generativemodel.md#generativemodelconstructor) | | Constructs a new instance of the GenerativeModel class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [generationConfig](./vertexai-preview.generativemodel.md#generativemodelgenerationconfig) | | [GenerationConfig](./vertexai-preview.generationconfig.md#generationconfig_interface) | | +| [model](./vertexai-preview.generativemodel.md#generativemodelmodel) | | string | | +| [requestOptions](./vertexai-preview.generativemodel.md#generativemodelrequestoptions) | | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | | +| [safetySettings](./vertexai-preview.generativemodel.md#generativemodelsafetysettings) | | [SafetySetting](./vertexai-preview.safetysetting.md#safetysetting_interface)\[\] | | +| [systemInstruction](./vertexai-preview.generativemodel.md#generativemodelsysteminstruction) | | [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.generativemodel.md#generativemodeltoolconfig) | | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.generativemodel.md#generativemodeltools) | | [Tool](./vertexai-preview.md#tool)\[\] | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [countTokens(request)](./vertexai-preview.generativemodel.md#generativemodelcounttokens) | | Counts the tokens in the provided request. | +| [generateContent(request)](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) | | Makes a single non-streaming call to the model and returns an object containing a single [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | +| [generateContentStream(request)](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream) | | 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. | +| [startChat(startChatParams)](./vertexai-preview.generativemodel.md#generativemodelstartchat) | | Gets a new [ChatSession](./vertexai-preview.chatsession.md#chatsession_class) instance which can be used for multi-turn chats. | + +## GenerativeModel.(constructor) + +Constructs a new instance of the `GenerativeModel` class + +Signature: + +```typescript +constructor(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vertexAI | [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | | +| modelParams | [ModelParams](./vertexai-preview.modelparams.md#modelparams_interface) | | +| requestOptions | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | | + +## GenerativeModel.generationConfig + +Signature: + +```typescript +generationConfig: GenerationConfig; +``` + +## GenerativeModel.model + +Signature: + +```typescript +model: string; +``` + +## GenerativeModel.requestOptions + +Signature: + +```typescript +requestOptions?: RequestOptions; +``` + +## GenerativeModel.safetySettings + +Signature: + +```typescript +safetySettings: SafetySetting[]; +``` + +## GenerativeModel.systemInstruction + +Signature: + +```typescript +systemInstruction?: Content; +``` + +## GenerativeModel.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## GenerativeModel.tools + +Signature: + +```typescript +tools?: Tool[]; +``` + +## GenerativeModel.countTokens() + +Counts the tokens in the provided request. + +Signature: + +```typescript +countTokens(request: CountTokensRequest | string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | [CountTokensRequest](./vertexai-preview.counttokensrequest.md#counttokensrequest_interface) \| string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[CountTokensResponse](./vertexai-preview.counttokensresponse.md#counttokensresponse_interface)> + +## GenerativeModel.generateContent() + +Makes a single non-streaming call to the model and returns an object containing a single [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). + +Signature: + +```typescript +generateContent(request: GenerateContentRequest | string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | [GenerateContentRequest](./vertexai-preview.generatecontentrequest.md#generatecontentrequest_interface) \| string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface)> + +## GenerativeModel.generateContentStream() + +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. + +Signature: + +```typescript +generateContentStream(request: GenerateContentRequest | string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | [GenerateContentRequest](./vertexai-preview.generatecontentrequest.md#generatecontentrequest_interface) \| string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface)> + +## GenerativeModel.startChat() + +Gets a new [ChatSession](./vertexai-preview.chatsession.md#chatsession_class) instance which can be used for multi-turn chats. + +Signature: + +```typescript +startChat(startChatParams?: StartChatParams): ChatSession; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| startChatParams | [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) | | + +Returns: + +[ChatSession](./vertexai-preview.chatsession.md#chatsession_class) + diff --git a/docs-devsite/vertexai-preview.groundingattribution.md b/docs-devsite/vertexai-preview.groundingattribution.md new file mode 100644 index 00000000000..2c7d2f09e0b --- /dev/null +++ b/docs-devsite/vertexai-preview.groundingattribution.md @@ -0,0 +1,59 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GroundingAttribution interface + +Signature: + +```typescript +export interface GroundingAttribution +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [confidenceScore](./vertexai-preview.groundingattribution.md#groundingattributionconfidencescore) | number | | +| [retrievedContext](./vertexai-preview.groundingattribution.md#groundingattributionretrievedcontext) | [RetrievedContextAttribution](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattribution_interface) | | +| [segment](./vertexai-preview.groundingattribution.md#groundingattributionsegment) | [Segment](./vertexai-preview.segment.md#segment_interface) | | +| [web](./vertexai-preview.groundingattribution.md#groundingattributionweb) | [WebAttribution](./vertexai-preview.webattribution.md#webattribution_interface) | | + +## GroundingAttribution.confidenceScore + +Signature: + +```typescript +confidenceScore?: number; +``` + +## GroundingAttribution.retrievedContext + +Signature: + +```typescript +retrievedContext?: RetrievedContextAttribution; +``` + +## GroundingAttribution.segment + +Signature: + +```typescript +segment: Segment; +``` + +## GroundingAttribution.web + +Signature: + +```typescript +web?: WebAttribution; +``` diff --git a/docs-devsite/vertexai-preview.groundingmetadata.md b/docs-devsite/vertexai-preview.groundingmetadata.md new file mode 100644 index 00000000000..5f40a00457d --- /dev/null +++ b/docs-devsite/vertexai-preview.groundingmetadata.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GroundingMetadata interface +Metadata returned to client when grounding is enabled. + +Signature: + +```typescript +export interface GroundingMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [groundingAttributions](./vertexai-preview.groundingmetadata.md#groundingmetadatagroundingattributions) | [GroundingAttribution](./vertexai-preview.groundingattribution.md#groundingattribution_interface)\[\] | | +| [retrievalQueries](./vertexai-preview.groundingmetadata.md#groundingmetadataretrievalqueries) | string\[\] | | +| [webSearchQueries](./vertexai-preview.groundingmetadata.md#groundingmetadatawebsearchqueries) | string\[\] | | + +## GroundingMetadata.groundingAttributions + +Signature: + +```typescript +groundingAttributions: GroundingAttribution[]; +``` + +## GroundingMetadata.retrievalQueries + +Signature: + +```typescript +retrievalQueries?: string[]; +``` + +## GroundingMetadata.webSearchQueries + +Signature: + +```typescript +webSearchQueries?: string[]; +``` diff --git a/docs-devsite/vertexai-preview.inlinedatapart.md b/docs-devsite/vertexai-preview.inlinedatapart.md new file mode 100644 index 00000000000..ae05f80ddb7 --- /dev/null +++ b/docs-devsite/vertexai-preview.inlinedatapart.md @@ -0,0 +1,71 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# InlineDataPart interface +Content part interface if the part represents an image. + +Signature: + +```typescript +export interface InlineDataPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.inlinedatapart.md#inlinedatapartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.inlinedatapart.md#inlinedatapartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.inlinedatapart.md#inlinedatapartinlinedata) | [GenerativeContentBlob](./vertexai-preview.generativecontentblob.md#generativecontentblob_interface) | | +| [text](./vertexai-preview.inlinedatapart.md#inlinedataparttext) | never | | +| [videoMetadata](./vertexai-preview.inlinedatapart.md#inlinedatapartvideometadata) | [VideoMetadata](./vertexai-preview.videometadata.md#videometadata_interface) | Applicable if inlineData is a video. | + +## InlineDataPart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## InlineDataPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## InlineDataPart.inlineData + +Signature: + +```typescript +inlineData: GenerativeContentBlob; +``` + +## InlineDataPart.text + +Signature: + +```typescript +text?: never; +``` + +## InlineDataPart.videoMetadata + +Applicable if `inlineData` is a video. + +Signature: + +```typescript +videoMetadata?: VideoMetadata; +``` diff --git a/docs-devsite/vertexai-preview.md b/docs-devsite/vertexai-preview.md new file mode 100644 index 00000000000..9faeed5ce08 --- /dev/null +++ b/docs-devsite/vertexai-preview.md @@ -0,0 +1,369 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# vertexai-preview package +The Firebase Vertex Web SDK. + +## Functions + +| Function | Description | +| --- | --- | +| function(app, ...) | +| [getVertexAI(app, options)](./vertexai-preview.md#getvertexai_04094cf) | Returns an [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. | +| function(vertexAI, ...) | +| [getGenerativeModel(vertexAI, modelParams, requestOptions)](./vertexai-preview.md#getgenerativemodel_e3037c9) | Returns a [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) class with methods for inference and other functionality. | + +## Classes + +| Class | Description | +| --- | --- | +| [ChatSession](./vertexai-preview.chatsession.md#chatsession_class) | ChatSession class that enables sending chat messages and stores history of sent and received messages so far. | +| [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) | Class for generative model APIs. | + +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [BlockReason](./vertexai-preview.md#blockreason) | Reason that a prompt was blocked. | +| [FinishReason](./vertexai-preview.md#finishreason) | Reason that a candidate finished. | +| [FunctionCallingMode](./vertexai-preview.md#functioncallingmode) | | +| [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype) | Contains the list of OpenAPI data types as defined by https://swagger.io/docs/specification/data-models/data-types/ | +| [HarmBlockMethod](./vertexai-preview.md#harmblockmethod) | | +| [HarmBlockThreshold](./vertexai-preview.md#harmblockthreshold) | Threshold above which a prompt or candidate will be blocked. | +| [HarmCategory](./vertexai-preview.md#harmcategory) | Harm categories that would cause prompts or candidates to be blocked. | +| [HarmProbability](./vertexai-preview.md#harmprobability) | Probability that a prompt or candidate matches a harm category. | +| [HarmSeverity](./vertexai-preview.md#harmseverity) | Harm severity levels. | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) | Base parameters for a number of methods. | +| [Citation](./vertexai-preview.citation.md#citation_interface) | A single citation. | +| [CitationMetadata](./vertexai-preview.citationmetadata.md#citationmetadata_interface) | Citation metadata that may be found on a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface). | +| [Content](./vertexai-preview.content.md#content_interface) | Content type for both prompts and response candidates. | +| [CountTokensRequest](./vertexai-preview.counttokensrequest.md#counttokensrequest_interface) | Params for calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens) | +| [CountTokensResponse](./vertexai-preview.counttokensresponse.md#counttokensresponse_interface) | Response from calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens). | +| [Date\_2](./vertexai-preview.date_2.md#date_2_interface) | Protobuf google.type.Date | +| [EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface) | Response object wrapped with helper methods. | +| [FileData](./vertexai-preview.filedata.md#filedata_interface) | Data pointing to a file uploaded on Google Cloud Storage. | +| [FileDataPart](./vertexai-preview.filedatapart.md#filedatapart_interface) | Content part interface if the part represents [FileData](./vertexai-preview.filedata.md#filedata_interface) | +| [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) | A predicted [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) returned from the model that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing the parameters and their values. | +| [FunctionCallingConfig](./vertexai-preview.functioncallingconfig.md#functioncallingconfig_interface) | | +| [FunctionCallPart](./vertexai-preview.functioncallpart.md#functioncallpart_interface) | Content part interface if the part represents a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface). | +| [FunctionDeclaration](./vertexai-preview.functiondeclaration.md#functiondeclaration_interface) | Structured representation of a function declaration as defined by the [OpenAPI 3.0 specification](https://spec.openapis.org/oas/v3.0.3). Included in this declaration are the function name and parameters. This FunctionDeclaration is a representation of a block of code that can be used as a Tool by the model and executed by the client. | +| [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | Schema for parameters passed to [FunctionDeclaration.parameters](./vertexai-preview.functiondeclaration.md#functiondeclarationparameters). | +| [FunctionDeclarationSchemaProperty](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemaproperty_interface) | Schema is used to define the format of input/output data. Represents a select subset of an OpenAPI 3.0 schema object. More fields may be added in the future as needed. | +| [FunctionDeclarationsTool](./vertexai-preview.functiondeclarationstool.md#functiondeclarationstool_interface) | A FunctionDeclarationsTool is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. | +| [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) | The result output from a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing any output from the function is used as context to the model. This should contain the result of a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) made based on model prediction. | +| [FunctionResponsePart](./vertexai-preview.functionresponsepart.md#functionresponsepart_interface) | Content part interface if the part represents [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface). | +| [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface) | A candidate returned as part of a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | +| [GenerateContentRequest](./vertexai-preview.generatecontentrequest.md#generatecontentrequest_interface) | Request sent through [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) | +| [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface) | Individual response from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) and [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream). generateContentStream() will return one in each chunk until the stream is done. | +| [GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface) | Result object returned from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) call. | +| [GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface) | Result object returned from [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream) call. Iterate over stream to get chunks as they come in and/or use the response promise to get the aggregated response when the stream is done. | +| [GenerationConfig](./vertexai-preview.generationconfig.md#generationconfig_interface) | Config options for content-related requests | +| [GenerativeContentBlob](./vertexai-preview.generativecontentblob.md#generativecontentblob_interface) | Interface for sending an image. | +| [GroundingAttribution](./vertexai-preview.groundingattribution.md#groundingattribution_interface) | | +| [GroundingMetadata](./vertexai-preview.groundingmetadata.md#groundingmetadata_interface) | Metadata returned to client when grounding is enabled. | +| [InlineDataPart](./vertexai-preview.inlinedatapart.md#inlinedatapart_interface) | Content part interface if the part represents an image. | +| [ModelParams](./vertexai-preview.modelparams.md#modelparams_interface) | Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). | +| [PromptFeedback](./vertexai-preview.promptfeedback.md#promptfeedback_interface) | If the prompt was blocked, this will be populated with blockReason and the relevant safetyRatings. | +| [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). | +| [RetrievedContextAttribution](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattribution_interface) | | +| [SafetyRating](./vertexai-preview.safetyrating.md#safetyrating_interface) | A safety rating associated with a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface) | +| [SafetySetting](./vertexai-preview.safetysetting.md#safetysetting_interface) | Safety setting that can be sent as part of request parameters. | +| [Segment](./vertexai-preview.segment.md#segment_interface) | | +| [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) | Params for [GenerativeModel.startChat()](./vertexai-preview.generativemodel.md#generativemodelstartchat). | +| [TextPart](./vertexai-preview.textpart.md#textpart_interface) | Content part interface if the part represents a text string. | +| [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | Tool config. This config is shared for all tools provided in the request. | +| [UsageMetadata](./vertexai-preview.usagemetadata.md#usagemetadata_interface) | Usage metadata about a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | +| [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | An instance of the Vertex AI for Firebase SDK. | +| [VertexAIOptions](./vertexai-preview.vertexaioptions.md#vertexaioptions_interface) | Options when initializing the Firebase Vertex AI SDK. | +| [VideoMetadata](./vertexai-preview.videometadata.md#videometadata_interface) | Describes the input video content. | +| [WebAttribution](./vertexai-preview.webattribution.md#webattribution_interface) | | + +## Variables + +| Variable | Description | +| --- | --- | +| [POSSIBLE\_ROLES](./vertexai-preview.md#possible_roles) | Possible roles. | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [Part](./vertexai-preview.md#part) | Content part - includes text, image/video, or function call/response part types. | +| [Role](./vertexai-preview.md#role) | Role is the producer of the content. | +| [Tool](./vertexai-preview.md#tool) | Defines a tool that model can call to access external knowledge. | + +## function(app, ...) + +### getVertexAI(app, options) {:#getvertexai_04094cf} + +Returns an [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. + +Signature: + +```typescript +export declare function getVertexAI(app?: FirebaseApp, options?: VertexAIOptions): VertexAI; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) to use. | +| options | [VertexAIOptions](./vertexai-preview.vertexaioptions.md#vertexaioptions_interface) | | + +Returns: + +[VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) + +## function(vertexAI, ...) + +### getGenerativeModel(vertexAI, modelParams, requestOptions) {:#getgenerativemodel_e3037c9} + +Returns a [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) class with methods for inference and other functionality. + +Signature: + +```typescript +export declare function getGenerativeModel(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions): GenerativeModel; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vertexAI | [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | | +| modelParams | [ModelParams](./vertexai-preview.modelparams.md#modelparams_interface) | | +| requestOptions | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | | + +Returns: + +[GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) + +## POSSIBLE\_ROLES + +Possible roles. + +Signature: + +```typescript +POSSIBLE_ROLES: readonly ["user", "model", "function", "system"] +``` + +## Part + +Content part - includes text, image/video, or function call/response part types. + +Signature: + +```typescript +export declare type Part = TextPart | InlineDataPart | FunctionCallPart | FunctionResponsePart | FileDataPart; +``` + +## Role + +Role is the producer of the content. + +Signature: + +```typescript +export declare type Role = (typeof POSSIBLE_ROLES)[number]; +``` + +## Tool + +Defines a tool that model can call to access external knowledge. + +Signature: + +```typescript +export declare type Tool = FunctionDeclarationsTool; +``` + +## BlockReason + +Reason that a prompt was blocked. + +Signature: + +```typescript +export declare enum BlockReason +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| BLOCKED\_REASON\_UNSPECIFIED | "BLOCKED_REASON_UNSPECIFIED" | | +| OTHER | "OTHER" | | +| SAFETY | "SAFETY" | | + +## FinishReason + +Reason that a candidate finished. + +Signature: + +```typescript +export declare enum FinishReason +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| FINISH\_REASON\_UNSPECIFIED | "FINISH_REASON_UNSPECIFIED" | | +| MAX\_TOKENS | "MAX_TOKENS" | | +| OTHER | "OTHER" | | +| RECITATION | "RECITATION" | | +| SAFETY | "SAFETY" | | +| STOP | "STOP" | | + +## FunctionCallingMode + + +Signature: + +```typescript +export declare enum FunctionCallingMode +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| ANY | "ANY" | | +| AUTO | "AUTO" | | +| MODE\_UNSPECIFIED | "MODE_UNSPECIFIED" | | +| NONE | "NONE" | | + +## FunctionDeclarationSchemaType + +Contains the list of OpenAPI data types as defined by https://swagger.io/docs/specification/data-models/data-types/ + +Signature: + +```typescript +export declare enum FunctionDeclarationSchemaType +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| ARRAY | "ARRAY" | Array type. | +| BOOLEAN | "BOOLEAN" | Boolean type. | +| INTEGER | "INTEGER" | Integer type. | +| NUMBER | "NUMBER" | Number type. | +| OBJECT | "OBJECT" | Object type. | +| STRING | "STRING" | String type. | + +## HarmBlockMethod + + +Signature: + +```typescript +export declare enum HarmBlockMethod +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_BLOCK\_METHOD\_UNSPECIFIED | "HARM_BLOCK_METHOD_UNSPECIFIED" | | +| PROBABILITY | "PROBABILITY" | | +| SEVERITY | "SEVERITY" | | + +## HarmBlockThreshold + +Threshold above which a prompt or candidate will be blocked. + +Signature: + +```typescript +export declare enum HarmBlockThreshold +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| BLOCK\_LOW\_AND\_ABOVE | "BLOCK_LOW_AND_ABOVE" | | +| BLOCK\_MEDIUM\_AND\_ABOVE | "BLOCK_MEDIUM_AND_ABOVE" | | +| BLOCK\_NONE | "BLOCK_NONE" | | +| BLOCK\_ONLY\_HIGH | "BLOCK_ONLY_HIGH" | | +| HARM\_BLOCK\_THRESHOLD\_UNSPECIFIED | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | | + +## HarmCategory + +Harm categories that would cause prompts or candidates to be blocked. + +Signature: + +```typescript +export declare enum HarmCategory +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_CATEGORY\_DANGEROUS\_CONTENT | "HARM_CATEGORY_DANGEROUS_CONTENT" | | +| HARM\_CATEGORY\_HARASSMENT | "HARM_CATEGORY_HARASSMENT" | | +| HARM\_CATEGORY\_HATE\_SPEECH | "HARM_CATEGORY_HATE_SPEECH" | | +| HARM\_CATEGORY\_SEXUALLY\_EXPLICIT | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | | +| HARM\_CATEGORY\_UNSPECIFIED | "HARM_CATEGORY_UNSPECIFIED" | | + +## HarmProbability + +Probability that a prompt or candidate matches a harm category. + +Signature: + +```typescript +export declare enum HarmProbability +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_PROBABILITY\_UNSPECIFIED | "HARM_PROBABILITY_UNSPECIFIED" | | +| HIGH | "HIGH" | | +| LOW | "LOW" | | +| MEDIUM | "MEDIUM" | | +| NEGLIGIBLE | "NEGLIGIBLE" | | + +## HarmSeverity + +Harm severity levels. + +Signature: + +```typescript +export declare enum HarmSeverity +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_SEVERITY\_HIGH | "HARM_SEVERITY_HIGH" | | +| HARM\_SEVERITY\_LOW | "HARM_SEVERITY_LOW" | | +| HARM\_SEVERITY\_MEDIUM | "HARM_SEVERITY_MEDIUM" | | +| HARM\_SEVERITY\_NEGLIGIBLE | "HARM_SEVERITY_NEGLIGIBLE" | | +| HARM\_SEVERITY\_UNSPECIFIED | "HARM_SEVERITY_UNSPECIFIED" | | + diff --git a/docs-devsite/vertexai-preview.modelparams.md b/docs-devsite/vertexai-preview.modelparams.md new file mode 100644 index 00000000000..34d68f86714 --- /dev/null +++ b/docs-devsite/vertexai-preview.modelparams.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ModelParams interface +Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). + +Signature: + +```typescript +export interface ModelParams extends BaseParams +``` +Extends: [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [model](./vertexai-preview.modelparams.md#modelparamsmodel) | string | | +| [systemInstruction](./vertexai-preview.modelparams.md#modelparamssysteminstruction) | string \| [Part](./vertexai-preview.md#part) \| [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.modelparams.md#modelparamstoolconfig) | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.modelparams.md#modelparamstools) | [Tool](./vertexai-preview.md#tool)\[\] | | + +## ModelParams.model + +Signature: + +```typescript +model: string; +``` + +## ModelParams.systemInstruction + +Signature: + +```typescript +systemInstruction?: string | Part | Content; +``` + +## ModelParams.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## ModelParams.tools + +Signature: + +```typescript +tools?: Tool[]; +``` diff --git a/docs-devsite/vertexai-preview.promptfeedback.md b/docs-devsite/vertexai-preview.promptfeedback.md new file mode 100644 index 00000000000..cb27f10c8c3 --- /dev/null +++ b/docs-devsite/vertexai-preview.promptfeedback.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# PromptFeedback interface +If the prompt was blocked, this will be populated with `blockReason` and the relevant `safetyRatings`. + +Signature: + +```typescript +export interface PromptFeedback +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [blockReason](./vertexai-preview.promptfeedback.md#promptfeedbackblockreason) | [BlockReason](./vertexai-preview.md#blockreason) | | +| [blockReasonMessage](./vertexai-preview.promptfeedback.md#promptfeedbackblockreasonmessage) | string | | +| [safetyRatings](./vertexai-preview.promptfeedback.md#promptfeedbacksafetyratings) | [SafetyRating](./vertexai-preview.safetyrating.md#safetyrating_interface)\[\] | | + +## PromptFeedback.blockReason + +Signature: + +```typescript +blockReason: BlockReason; +``` + +## PromptFeedback.blockReasonMessage + +Signature: + +```typescript +blockReasonMessage?: string; +``` + +## PromptFeedback.safetyRatings + +Signature: + +```typescript +safetyRatings: SafetyRating[]; +``` diff --git a/docs-devsite/vertexai-preview.requestoptions.md b/docs-devsite/vertexai-preview.requestoptions.md new file mode 100644 index 00000000000..550ec44ce96 --- /dev/null +++ b/docs-devsite/vertexai-preview.requestoptions.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# RequestOptions interface +Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). + +Signature: + +```typescript +export interface RequestOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [baseUrl](./vertexai-preview.requestoptions.md#requestoptionsbaseurl) | string | Base url for endpoint. Defaults to https://firebaseml.googleapis.com | +| [timeout](./vertexai-preview.requestoptions.md#requestoptionstimeout) | number | Request timeout in milliseconds. | + +## RequestOptions.baseUrl + +Base url for endpoint. Defaults to https://firebaseml.googleapis.com + +Signature: + +```typescript +baseUrl?: string; +``` + +## RequestOptions.timeout + +Request timeout in milliseconds. + +Signature: + +```typescript +timeout?: number; +``` diff --git a/docs-devsite/vertexai-preview.retrievedcontextattribution.md b/docs-devsite/vertexai-preview.retrievedcontextattribution.md new file mode 100644 index 00000000000..0a121cdc004 --- /dev/null +++ b/docs-devsite/vertexai-preview.retrievedcontextattribution.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# RetrievedContextAttribution interface + +Signature: + +```typescript +export interface RetrievedContextAttribution +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [title](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattributiontitle) | string | | +| [uri](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattributionuri) | string | | + +## RetrievedContextAttribution.title + +Signature: + +```typescript +title: string; +``` + +## RetrievedContextAttribution.uri + +Signature: + +```typescript +uri: string; +``` diff --git a/docs-devsite/vertexai-preview.safetyrating.md b/docs-devsite/vertexai-preview.safetyrating.md new file mode 100644 index 00000000000..65b1bc8fb42 --- /dev/null +++ b/docs-devsite/vertexai-preview.safetyrating.md @@ -0,0 +1,78 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SafetyRating interface +A safety rating associated with a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface) + +Signature: + +```typescript +export interface SafetyRating +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [blocked](./vertexai-preview.safetyrating.md#safetyratingblocked) | boolean | | +| [category](./vertexai-preview.safetyrating.md#safetyratingcategory) | [HarmCategory](./vertexai-preview.md#harmcategory) | | +| [probability](./vertexai-preview.safetyrating.md#safetyratingprobability) | [HarmProbability](./vertexai-preview.md#harmprobability) | | +| [probabilityScore](./vertexai-preview.safetyrating.md#safetyratingprobabilityscore) | number | | +| [severity](./vertexai-preview.safetyrating.md#safetyratingseverity) | [HarmSeverity](./vertexai-preview.md#harmseverity) | | +| [severityScore](./vertexai-preview.safetyrating.md#safetyratingseverityscore) | number | | + +## SafetyRating.blocked + +Signature: + +```typescript +blocked: boolean; +``` + +## SafetyRating.category + +Signature: + +```typescript +category: HarmCategory; +``` + +## SafetyRating.probability + +Signature: + +```typescript +probability: HarmProbability; +``` + +## SafetyRating.probabilityScore + +Signature: + +```typescript +probabilityScore: number; +``` + +## SafetyRating.severity + +Signature: + +```typescript +severity: HarmSeverity; +``` + +## SafetyRating.severityScore + +Signature: + +```typescript +severityScore: number; +``` diff --git a/docs-devsite/vertexai-preview.safetysetting.md b/docs-devsite/vertexai-preview.safetysetting.md new file mode 100644 index 00000000000..78678315805 --- /dev/null +++ b/docs-devsite/vertexai-preview.safetysetting.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SafetySetting interface +Safety setting that can be sent as part of request parameters. + +Signature: + +```typescript +export interface SafetySetting +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [category](./vertexai-preview.safetysetting.md#safetysettingcategory) | [HarmCategory](./vertexai-preview.md#harmcategory) | | +| [method](./vertexai-preview.safetysetting.md#safetysettingmethod) | [HarmBlockMethod](./vertexai-preview.md#harmblockmethod) | | +| [threshold](./vertexai-preview.safetysetting.md#safetysettingthreshold) | [HarmBlockThreshold](./vertexai-preview.md#harmblockthreshold) | | + +## SafetySetting.category + +Signature: + +```typescript +category: HarmCategory; +``` + +## SafetySetting.method + +Signature: + +```typescript +method: HarmBlockMethod; +``` + +## SafetySetting.threshold + +Signature: + +```typescript +threshold: HarmBlockThreshold; +``` diff --git a/docs-devsite/vertexai-preview.segment.md b/docs-devsite/vertexai-preview.segment.md new file mode 100644 index 00000000000..c64bc3ffcda --- /dev/null +++ b/docs-devsite/vertexai-preview.segment.md @@ -0,0 +1,50 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Segment interface + +Signature: + +```typescript +export interface Segment +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [endIndex](./vertexai-preview.segment.md#segmentendindex) | number | | +| [partIndex](./vertexai-preview.segment.md#segmentpartindex) | number | | +| [startIndex](./vertexai-preview.segment.md#segmentstartindex) | number | | + +## Segment.endIndex + +Signature: + +```typescript +endIndex: number; +``` + +## Segment.partIndex + +Signature: + +```typescript +partIndex: number; +``` + +## Segment.startIndex + +Signature: + +```typescript +startIndex: number; +``` diff --git a/docs-devsite/vertexai-preview.startchatparams.md b/docs-devsite/vertexai-preview.startchatparams.md new file mode 100644 index 00000000000..f422f7a1ff0 --- /dev/null +++ b/docs-devsite/vertexai-preview.startchatparams.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# StartChatParams interface +Params for [GenerativeModel.startChat()](./vertexai-preview.generativemodel.md#generativemodelstartchat). + +Signature: + +```typescript +export interface StartChatParams extends BaseParams +``` +Extends: [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [history](./vertexai-preview.startchatparams.md#startchatparamshistory) | [Content](./vertexai-preview.content.md#content_interface)\[\] | | +| [systemInstruction](./vertexai-preview.startchatparams.md#startchatparamssysteminstruction) | string \| [Part](./vertexai-preview.md#part) \| [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.startchatparams.md#startchatparamstoolconfig) | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.startchatparams.md#startchatparamstools) | [Tool](./vertexai-preview.md#tool)\[\] | | + +## StartChatParams.history + +Signature: + +```typescript +history?: Content[]; +``` + +## StartChatParams.systemInstruction + +Signature: + +```typescript +systemInstruction?: string | Part | Content; +``` + +## StartChatParams.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## StartChatParams.tools + +Signature: + +```typescript +tools?: Tool[]; +``` diff --git a/docs-devsite/vertexai-preview.textpart.md b/docs-devsite/vertexai-preview.textpart.md new file mode 100644 index 00000000000..206168180b2 --- /dev/null +++ b/docs-devsite/vertexai-preview.textpart.md @@ -0,0 +1,60 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# TextPart interface +Content part interface if the part represents a text string. + +Signature: + +```typescript +export interface TextPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.textpart.md#textpartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.textpart.md#textpartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.textpart.md#textpartinlinedata) | never | | +| [text](./vertexai-preview.textpart.md#textparttext) | string | | + +## TextPart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## TextPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## TextPart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## TextPart.text + +Signature: + +```typescript +text: string; +``` diff --git a/docs-devsite/vertexai-preview.toolconfig.md b/docs-devsite/vertexai-preview.toolconfig.md new file mode 100644 index 00000000000..4278eef509b --- /dev/null +++ b/docs-devsite/vertexai-preview.toolconfig.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ToolConfig interface +Tool config. This config is shared for all tools provided in the request. + +Signature: + +```typescript +export interface ToolConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCallingConfig](./vertexai-preview.toolconfig.md#toolconfigfunctioncallingconfig) | [FunctionCallingConfig](./vertexai-preview.functioncallingconfig.md#functioncallingconfig_interface) | | + +## ToolConfig.functionCallingConfig + +Signature: + +```typescript +functionCallingConfig: FunctionCallingConfig; +``` diff --git a/docs-devsite/vertexai-preview.usagemetadata.md b/docs-devsite/vertexai-preview.usagemetadata.md new file mode 100644 index 00000000000..2829c9dbd5d --- /dev/null +++ b/docs-devsite/vertexai-preview.usagemetadata.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# UsageMetadata interface +Usage metadata about a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). + +Signature: + +```typescript +export interface UsageMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [candidatesTokenCount](./vertexai-preview.usagemetadata.md#usagemetadatacandidatestokencount) | number | | +| [promptTokenCount](./vertexai-preview.usagemetadata.md#usagemetadataprompttokencount) | number | | +| [totalTokenCount](./vertexai-preview.usagemetadata.md#usagemetadatatotaltokencount) | number | | + +## UsageMetadata.candidatesTokenCount + +Signature: + +```typescript +candidatesTokenCount: number; +``` + +## UsageMetadata.promptTokenCount + +Signature: + +```typescript +promptTokenCount: number; +``` + +## UsageMetadata.totalTokenCount + +Signature: + +```typescript +totalTokenCount: number; +``` diff --git a/docs-devsite/vertexai-preview.vertexai.md b/docs-devsite/vertexai-preview.vertexai.md new file mode 100644 index 00000000000..35991f2be12 --- /dev/null +++ b/docs-devsite/vertexai-preview.vertexai.md @@ -0,0 +1,44 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VertexAI interface +An instance of the Vertex AI for Firebase SDK. + +Signature: + +```typescript +export interface VertexAI +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [app](./vertexai-preview.vertexai.md#vertexaiapp) | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) this [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance is associated with. | +| [location](./vertexai-preview.vertexai.md#vertexailocation) | string | | + +## VertexAI.app + +The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) this [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance is associated with. + +Signature: + +```typescript +app: FirebaseApp; +``` + +## VertexAI.location + +Signature: + +```typescript +location: string; +``` diff --git a/docs-devsite/vertexai-preview.vertexaioptions.md b/docs-devsite/vertexai-preview.vertexaioptions.md new file mode 100644 index 00000000000..0099d6bae7b --- /dev/null +++ b/docs-devsite/vertexai-preview.vertexaioptions.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VertexAIOptions interface +Options when initializing the Firebase Vertex AI SDK. + +Signature: + +```typescript +export interface VertexAIOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [location](./vertexai-preview.vertexaioptions.md#vertexaioptionslocation) | string | | + +## VertexAIOptions.location + +Signature: + +```typescript +location?: string; +``` diff --git a/docs-devsite/vertexai-preview.videometadata.md b/docs-devsite/vertexai-preview.videometadata.md new file mode 100644 index 00000000000..04d8883bae9 --- /dev/null +++ b/docs-devsite/vertexai-preview.videometadata.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VideoMetadata interface +Describes the input video content. + +Signature: + +```typescript +export interface VideoMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [endOffset](./vertexai-preview.videometadata.md#videometadataendoffset) | string | The end offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. | +| [startOffset](./vertexai-preview.videometadata.md#videometadatastartoffset) | string | The start offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. | + +## VideoMetadata.endOffset + +The end offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. + +Signature: + +```typescript +endOffset: string; +``` + +## VideoMetadata.startOffset + +The start offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. + +Signature: + +```typescript +startOffset: string; +``` diff --git a/docs-devsite/vertexai-preview.webattribution.md b/docs-devsite/vertexai-preview.webattribution.md new file mode 100644 index 00000000000..5db6f94b82e --- /dev/null +++ b/docs-devsite/vertexai-preview.webattribution.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# WebAttribution interface + +Signature: + +```typescript +export interface WebAttribution +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [title](./vertexai-preview.webattribution.md#webattributiontitle) | string | | +| [uri](./vertexai-preview.webattribution.md#webattributionuri) | string | | + +## WebAttribution.title + +Signature: + +```typescript +title: string; +``` + +## WebAttribution.uri + +Signature: + +```typescript +uri: string; +``` diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index 64eeccba371..366ace88e12 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -20,7 +20,7 @@ import { FirebaseApp } from '@firebase/app'; export * from './types'; /** - * An instance of Firebase Vertex AI. + * An instance of the Vertex AI for Firebase SDK. * @public */ export interface VertexAI { diff --git a/packages/vertexai/src/types/content.ts b/packages/vertexai/src/types/content.ts index 4a1b82f4e6d..ad2906671e4 100644 --- a/packages/vertexai/src/types/content.ts +++ b/packages/vertexai/src/types/content.ts @@ -27,7 +27,8 @@ export interface Content { } /** - * Content part - includes text, image/video, or function call/response part types. + * Content part - includes text, image/video, or function call/response + * part types. * @public */ export type Part = @@ -115,8 +116,8 @@ export interface FileDataPart { } /** - * A predicted [FunctionCall] returned from the model - * that contains a string representing the [FunctionDeclaration.name] + * A predicted {@link FunctionCall} returned from the model + * that contains a string representing the {@link FunctionDeclaration.name} * and a structured JSON object containing the parameters and their values. * @public */ @@ -126,11 +127,11 @@ export interface FunctionCall { } /** - * The result output from a [FunctionCall] that contains a string - * representing the [FunctionDeclaration.name] + * The result output from a {@link FunctionCall} that contains a string + * representing the {@link FunctionDeclaration.name} * and a structured JSON object containing any output * from the function is used as context to the model. - * This should contain the result of a [FunctionCall] + * This should contain the result of a {@link FunctionCall} * made based on model prediction. * @public */ diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index 079d72f348f..748f6ee140a 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -44,7 +44,7 @@ export interface ModelParams extends BaseParams { } /** - * Request sent to `generateContent` endpoint. + * Request sent through {@link GenerativeModel.generateContent} * @public */ export interface GenerateContentRequest extends BaseParams { @@ -121,9 +121,10 @@ export declare type Tool = FunctionDeclarationsTool; /** * Structured representation of a function declaration as defined by the - * [OpenAPI 3.0 specification](https://spec.openapis.org/oas/v3.0.3). Included + * {@link https://spec.openapis.org/oas/v3.0.3 | OpenAPI 3.0 specification}. + * Included * in this declaration are the function name and parameters. This - * FunctionDeclaration is a representation of a block of code that can be used + * `FunctionDeclaration` is a representation of a block of code that can be used * as a Tool by the model and executed by the client. * @public */ @@ -148,7 +149,7 @@ export declare interface FunctionDeclaration { } /** - * A FunctionDeclarationsTool is a piece of code that enables the system to + * A `FunctionDeclarationsTool` is a piece of code that enables the system to * interact with external systems to perform an action, or set of actions, * outside of knowledge and scope of the model. * @public diff --git a/packages/vertexai/src/types/responses.ts b/packages/vertexai/src/types/responses.ts index c46a243edef..0a4557fb055 100644 --- a/packages/vertexai/src/types/responses.ts +++ b/packages/vertexai/src/types/responses.ts @@ -25,7 +25,7 @@ import { } from './enums'; /** - * Result object returned from generateContent() call. + * Result object returned from {@link GenerativeModel.generateContent} call. * * @public */ @@ -34,7 +34,7 @@ export interface GenerateContentResult { } /** - * Result object returned from generateContentStream() call. + * Result object returned from {@link GenerativeModel.generateContentStream} call. * Iterate over `stream` to get chunks as they come in and/or * use the `response` promise to get the aggregated response when * the stream is done. From d79a131c45dd84349839b253cc1c722014995b81 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 8 May 2024 10:18:13 -0700 Subject: [PATCH 26/28] make test() accessor handle mixed-parts responses (#8229) --- .../src/requests/response-helpers.test.ts | 121 +++++++++++++----- .../vertexai/src/requests/response-helpers.ts | 16 ++- 2 files changed, 99 insertions(+), 38 deletions(-) diff --git a/packages/vertexai/src/requests/response-helpers.test.ts b/packages/vertexai/src/requests/response-helpers.test.ts index 79bc6f01d6f..91a60d2cfce 100644 --- a/packages/vertexai/src/requests/response-helpers.test.ts +++ b/packages/vertexai/src/requests/response-helpers.test.ts @@ -40,54 +40,85 @@ const fakeResponseText: GenerateContentResponse = { ] }; +const functionCallPart1 = { + functionCall: { + name: 'find_theaters', + args: { + location: 'Mountain View, CA', + movie: 'Barbie' + } + } +}; + +const functionCallPart2 = { + functionCall: { + name: 'find_times', + args: { + location: 'Mountain View, CA', + movie: 'Barbie', + time: '20:00' + } + } +}; + const fakeResponseFunctionCall: GenerateContentResponse = { candidates: [ { index: 0, content: { role: 'model', - parts: [ - { - functionCall: { - name: 'find_theaters', - args: { - location: 'Mountain View, CA', - movie: 'Barbie' - } - } - } - ] + parts: [functionCallPart1] } } ] }; const fakeResponseFunctionCalls: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [functionCallPart1, functionCallPart2] + } + } + ] +}; + +const fakeResponseMixed1: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [{ text: 'some text' }, functionCallPart2] + } + } + ] +}; + +const fakeResponseMixed2: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [functionCallPart1, { text: 'some text' }] + } + } + ] +}; + +const fakeResponseMixed3: GenerateContentResponse = { candidates: [ { index: 0, content: { role: 'model', parts: [ - { - functionCall: { - name: 'find_theaters', - args: { - location: 'Mountain View, CA', - movie: 'Barbie' - } - } - }, - { - functionCall: { - name: 'find_times', - args: { - location: 'Mountain View, CA', - movie: 'Barbie', - time: '20:00' - } - } - } + { text: 'some text' }, + functionCallPart1, + { text: ' and more text' } ] } } @@ -109,19 +140,43 @@ describe('response-helpers methods', () => { it('good response text', async () => { const enhancedResponse = addHelpers(fakeResponseText); expect(enhancedResponse.text()).to.equal('Some text and some more text'); + expect(enhancedResponse.functionCalls()).to.be.undefined; }); it('good response functionCall', async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCall); + expect(enhancedResponse.text()).to.equal(''); expect(enhancedResponse.functionCalls()).to.deep.equal([ - fakeResponseFunctionCall.candidates?.[0].content.parts[0].functionCall + functionCallPart1.functionCall ]); }); it('good response functionCalls', async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCalls); + expect(enhancedResponse.text()).to.equal(''); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall, + functionCallPart2.functionCall + ]); + }); + it('good response text/functionCall', async () => { + const enhancedResponse = addHelpers(fakeResponseMixed1); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart2.functionCall + ]); + expect(enhancedResponse.text()).to.equal('some text'); + }); + it('good response functionCall/text', async () => { + const enhancedResponse = addHelpers(fakeResponseMixed2); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall + ]); + expect(enhancedResponse.text()).to.equal('some text'); + }); + it('good response text/functionCall/text', async () => { + const enhancedResponse = addHelpers(fakeResponseMixed3); expect(enhancedResponse.functionCalls()).to.deep.equal([ - fakeResponseFunctionCalls.candidates?.[0].content.parts[0].functionCall, - fakeResponseFunctionCalls.candidates?.[0].content.parts[1].functionCall + functionCallPart1.functionCall ]); + expect(enhancedResponse.text()).to.equal('some text and more text'); }); it('bad response safety', async () => { const enhancedResponse = addHelpers(badFakeResponse); diff --git a/packages/vertexai/src/requests/response-helpers.ts b/packages/vertexai/src/requests/response-helpers.ts index 17a0071b008..dc49123420f 100644 --- a/packages/vertexai/src/requests/response-helpers.ts +++ b/packages/vertexai/src/requests/response-helpers.ts @@ -85,13 +85,19 @@ export function addHelpers( } /** - * Returns text of first candidate. + * Returns all text found in all parts of first candidate. */ export function getText(response: GenerateContentResponse): string { - if (response.candidates?.[0].content?.parts?.[0]?.text) { - return response.candidates[0].content.parts - .map(({ text }) => text) - .join(''); + const textStrings = []; + if (response.candidates?.[0].content?.parts) { + for (const part of response.candidates?.[0].content?.parts) { + if (part.text) { + textStrings.push(part.text); + } + } + } + if (textStrings.length > 0) { + return textStrings.join(''); } else { return ''; } From c66445cb75b4fa32ecfbfeab8eac9fa34d622568 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 8 May 2024 11:45:55 -0700 Subject: [PATCH 27/28] Fix doc comments (#8234) * fix doc comments * fix one more comment * Update docs-devsite/vertexai-preview.md Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> --------- Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> --- docs-devsite/index.md | 2 +- docs-devsite/vertexai-preview.md | 8 ++++---- docs-devsite/vertexai-preview.vertexaioptions.md | 2 +- packages/vertexai/src/api.ts | 2 +- packages/vertexai/src/index.ts | 2 +- packages/vertexai/src/public-types.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs-devsite/index.md b/docs-devsite/index.md index d777885ef1a..2c22b58d80c 100644 --- a/docs-devsite/index.md +++ b/docs-devsite/index.md @@ -27,5 +27,5 @@ https://github.com/firebase/firebase-js-sdk | [@firebase/performance](./performance.md#performance_package) | The Firebase Performance Monitoring Web SDK. This SDK does not work in a Node.js environment. | | [@firebase/remote-config](./remote-config.md#remote-config_package) | The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environment. | | [@firebase/storage](./storage.md#storage_package) | Cloud Storage for Firebase | -| [@firebase/vertexai-preview](./vertexai-preview.md#vertexai-preview_package) | The Firebase Vertex Web SDK. | +| [@firebase/vertexai-preview](./vertexai-preview.md#vertexai-preview_package) | The Vertex AI For Firebase Web SDK. | diff --git a/docs-devsite/vertexai-preview.md b/docs-devsite/vertexai-preview.md index 9faeed5ce08..1aba07d3719 100644 --- a/docs-devsite/vertexai-preview.md +++ b/docs-devsite/vertexai-preview.md @@ -10,14 +10,14 @@ https://github.com/firebase/firebase-js-sdk {% endcomment %} # vertexai-preview package -The Firebase Vertex Web SDK. +The Vertex AI For Firebase Web SDK. ## Functions | Function | Description | | --- | --- | | function(app, ...) | -| [getVertexAI(app, options)](./vertexai-preview.md#getvertexai_04094cf) | Returns an [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. | +| [getVertexAI(app, options)](./vertexai-preview.md#getvertexai_04094cf) | Returns a [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. | | function(vertexAI, ...) | | [getGenerativeModel(vertexAI, modelParams, requestOptions)](./vertexai-preview.md#getgenerativemodel_e3037c9) | Returns a [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) class with methods for inference and other functionality. | @@ -87,7 +87,7 @@ The Firebase Vertex Web SDK. | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | Tool config. This config is shared for all tools provided in the request. | | [UsageMetadata](./vertexai-preview.usagemetadata.md#usagemetadata_interface) | Usage metadata about a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | | [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | An instance of the Vertex AI for Firebase SDK. | -| [VertexAIOptions](./vertexai-preview.vertexaioptions.md#vertexaioptions_interface) | Options when initializing the Firebase Vertex AI SDK. | +| [VertexAIOptions](./vertexai-preview.vertexaioptions.md#vertexaioptions_interface) | Options when initializing the Vertex AI for Firebase SDK. | | [VideoMetadata](./vertexai-preview.videometadata.md#videometadata_interface) | Describes the input video content. | | [WebAttribution](./vertexai-preview.webattribution.md#webattribution_interface) | | @@ -109,7 +109,7 @@ The Firebase Vertex Web SDK. ### getVertexAI(app, options) {:#getvertexai_04094cf} -Returns an [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. +Returns a [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. Signature: diff --git a/docs-devsite/vertexai-preview.vertexaioptions.md b/docs-devsite/vertexai-preview.vertexaioptions.md index 0099d6bae7b..320132c22f9 100644 --- a/docs-devsite/vertexai-preview.vertexaioptions.md +++ b/docs-devsite/vertexai-preview.vertexaioptions.md @@ -10,7 +10,7 @@ https://github.com/firebase/firebase-js-sdk {% endcomment %} # VertexAIOptions interface -Options when initializing the Firebase Vertex AI SDK. +Options when initializing the Vertex AI for Firebase SDK. Signature: diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts index 38409d598ba..5b9620969b8 100644 --- a/packages/vertexai/src/api.ts +++ b/packages/vertexai/src/api.ts @@ -36,7 +36,7 @@ declare module '@firebase/component' { } /** - * Returns an {@link VertexAI} instance for the given app. + * Returns a {@link VertexAI} instance for the given app. * * @public * diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts index 507d3e38697..b4c78b0731c 100644 --- a/packages/vertexai/src/index.ts +++ b/packages/vertexai/src/index.ts @@ -1,5 +1,5 @@ /** - * The Firebase Vertex Web SDK. + * The Vertex AI For Firebase Web SDK. * * @packageDocumentation */ diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts index 366ace88e12..5577bc69c85 100644 --- a/packages/vertexai/src/public-types.ts +++ b/packages/vertexai/src/public-types.ts @@ -32,7 +32,7 @@ export interface VertexAI { } /** - * Options when initializing the Firebase Vertex AI SDK. + * Options when initializing the Vertex AI for Firebase SDK. * @public */ export interface VertexAIOptions { From f6a2b882cd5b7f5856cb55ae51f31cbae5a69d5c Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 8 May 2024 16:33:42 -0400 Subject: [PATCH 28/28] [Vertex AI] Add a responseMimeType to generationConfig (#8235) Provide Apps with an ability to configure the responseMimeType. This enables applications to request that VertexAI returns the response in a JSON format instead of a normal textual format. --- common/api-review/vertexai-preview.api.md | 1 + docs-devsite/vertexai-preview.generationconfig.md | 11 +++++++++++ packages/vertexai/src/types/requests.ts | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/common/api-review/vertexai-preview.api.md b/common/api-review/vertexai-preview.api.md index 96d8ec70050..b3ae09e8dc7 100644 --- a/common/api-review/vertexai-preview.api.md +++ b/common/api-review/vertexai-preview.api.md @@ -311,6 +311,7 @@ export interface GenerationConfig { maxOutputTokens?: number; // (undocumented) presencePenalty?: number; + responseMimeType?: string; // (undocumented) stopSequences?: string[]; // (undocumented) diff --git a/docs-devsite/vertexai-preview.generationconfig.md b/docs-devsite/vertexai-preview.generationconfig.md index 0aa8e5567f7..3b00214d88b 100644 --- a/docs-devsite/vertexai-preview.generationconfig.md +++ b/docs-devsite/vertexai-preview.generationconfig.md @@ -26,6 +26,7 @@ export interface GenerationConfig | [frequencyPenalty](./vertexai-preview.generationconfig.md#generationconfigfrequencypenalty) | number | | | [maxOutputTokens](./vertexai-preview.generationconfig.md#generationconfigmaxoutputtokens) | number | | | [presencePenalty](./vertexai-preview.generationconfig.md#generationconfigpresencepenalty) | number | | +| [responseMimeType](./vertexai-preview.generationconfig.md#generationconfigresponsemimetype) | string | Output response mimetype of the generated candidate text. Supported mimetype: text/plain: (default) Text output. application/json: JSON response in the candidates. The model needs to be prompted to output the appropriate response type, otherwise the behavior is undefined. This is a preview feature. | | [stopSequences](./vertexai-preview.generationconfig.md#generationconfigstopsequences) | string\[\] | | | [temperature](./vertexai-preview.generationconfig.md#generationconfigtemperature) | number | | | [topK](./vertexai-preview.generationconfig.md#generationconfigtopk) | number | | @@ -63,6 +64,16 @@ maxOutputTokens?: number; presencePenalty?: number; ``` +## GenerationConfig.responseMimeType + +Output response mimetype of the generated candidate text. Supported mimetype: `text/plain`: (default) Text output. `application/json`: JSON response in the candidates. The model needs to be prompted to output the appropriate response type, otherwise the behavior is undefined. This is a preview feature. + +Signature: + +```typescript +responseMimeType?: string; +``` + ## GenerationConfig.stopSequences Signature: diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts index 748f6ee140a..5da8f05b6bb 100644 --- a/packages/vertexai/src/types/requests.ts +++ b/packages/vertexai/src/types/requests.ts @@ -77,6 +77,16 @@ export interface GenerationConfig { topK?: number; presencePenalty?: number; frequencyPenalty?: number; + /** + * Output response mimetype of the generated candidate text. + * Supported mimetype: + * `text/plain`: (default) Text output. + * `application/json`: JSON response in the candidates. + * The model needs to be prompted to output the appropriate response type, + * otherwise the behavior is undefined. + * This is a preview feature. + */ + responseMimeType?: string; } /**