From a6853957b1665dad0a7416611bc96860a3951ede Mon Sep 17 00:00:00 2001 From: Erik Eldridge Date: Tue, 22 Apr 2025 16:25:28 -0700 Subject: [PATCH 1/3] Return true unconditionally if mode is only_on_device --- .../src/methods/chrome-adapter.test.ts | 66 ++----------------- .../vertexai/src/methods/chrome-adapter.ts | 30 ++++----- 2 files changed, 19 insertions(+), 77 deletions(-) diff --git a/packages/vertexai/src/methods/chrome-adapter.test.ts b/packages/vertexai/src/methods/chrome-adapter.test.ts index 1f96019e177..d68b74c3a8e 100644 --- a/packages/vertexai/src/methods/chrome-adapter.test.ts +++ b/packages/vertexai/src/methods/chrome-adapter.test.ts @@ -61,19 +61,8 @@ describe('ChromeAdapter', () => { }) ).to.be.false; }); - it('returns false if AI API is undefined', async () => { - const adapter = new ChromeAdapter(undefined, 'prefer_on_device'); - expect( - await adapter.isAvailable({ - contents: [] - }) - ).to.be.false; - }); it('returns false if LanguageModel API is undefined', async () => { - const adapter = new ChromeAdapter( - {} as LanguageModel, - 'prefer_on_device' - ); + const adapter = new ChromeAdapter(undefined, 'prefer_on_device'); expect( await adapter.isAvailable({ contents: [] @@ -82,7 +71,9 @@ describe('ChromeAdapter', () => { }); it('returns false if request contents empty', async () => { const adapter = new ChromeAdapter( - {} as LanguageModel, + { + availability: async () => Availability.available + } as LanguageModel, 'prefer_on_device' ); expect( @@ -93,7 +84,9 @@ describe('ChromeAdapter', () => { }); it('returns false if request content has function role', async () => { const adapter = new ChromeAdapter( - {} as LanguageModel, + { + availability: async () => Availability.available + } as LanguageModel, 'prefer_on_device' ); expect( @@ -107,51 +100,6 @@ describe('ChromeAdapter', () => { }) ).to.be.false; }); - it('returns false if request system instruction has function role', async () => { - const adapter = new ChromeAdapter( - {} as LanguageModel, - 'prefer_on_device' - ); - expect( - await adapter.isAvailable({ - contents: [], - systemInstruction: { - role: 'function', - parts: [] - } - }) - ).to.be.false; - }); - it('returns false if request system instruction has multiple parts', async () => { - const adapter = new ChromeAdapter( - {} as LanguageModel, - 'prefer_on_device' - ); - expect( - await adapter.isAvailable({ - contents: [], - systemInstruction: { - role: 'function', - parts: [{ text: 'a' }, { text: 'b' }] - } - }) - ).to.be.false; - }); - it('returns false if request system instruction has non-text part', async () => { - const adapter = new ChromeAdapter( - {} as LanguageModel, - 'prefer_on_device' - ); - expect( - await adapter.isAvailable({ - contents: [], - systemInstruction: { - role: 'function', - parts: [{ inlineData: { mimeType: 'a', data: 'b' } }] - } - }) - ).to.be.false; - }); it('returns true if model is readily available', async () => { const languageModelProvider = { availability: () => Promise.resolve(Availability.available) diff --git a/packages/vertexai/src/methods/chrome-adapter.ts b/packages/vertexai/src/methods/chrome-adapter.ts index 3f641a47ee4..af382441b16 100644 --- a/packages/vertexai/src/methods/chrome-adapter.ts +++ b/packages/vertexai/src/methods/chrome-adapter.ts @@ -60,29 +60,23 @@ export class ChromeAdapter { * separation of concerns.

*/ async isAvailable(request: GenerateContentRequest): Promise { - // Returns false if we should only use in-cloud inference. if (this.mode === 'only_in_cloud') { return false; } - // Returns false if the on-device inference API is undefined.; - if (!this.languageModelProvider) { - return false; + console.log(this.languageModelProvider); + const availability = await this.languageModelProvider?.availability(); + if (availability === Availability.downloadable) { + // Triggers async model download. + this.download(); } - // Returns false if the request can't be run on-device. - if (!ChromeAdapter.isOnDeviceRequest(request)) { - return false; - } - const availability = await this.languageModelProvider.availability(); - switch (availability) { - case Availability.available: - // Returns true only if a model is immediately available. - return true; - case Availability.downloadable: - // Triggers async download if model is downloadable. - this.download(); - default: - return false; + if (this.mode === 'only_on_device') { + return true; } + // Applies prefer_on_device logic. + return ( + availability === Availability.available && + ChromeAdapter.isOnDeviceRequest(request) + ); } /** From 89794825c93c3cf8644c61a99076eec16e284c1d Mon Sep 17 00:00:00 2001 From: Erik Eldridge Date: Tue, 22 Apr 2025 16:36:44 -0700 Subject: [PATCH 2/3] Remove stray log statement --- packages/vertexai/src/methods/chrome-adapter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vertexai/src/methods/chrome-adapter.ts b/packages/vertexai/src/methods/chrome-adapter.ts index af382441b16..0918369384a 100644 --- a/packages/vertexai/src/methods/chrome-adapter.ts +++ b/packages/vertexai/src/methods/chrome-adapter.ts @@ -63,10 +63,9 @@ export class ChromeAdapter { if (this.mode === 'only_in_cloud') { return false; } - console.log(this.languageModelProvider); const availability = await this.languageModelProvider?.availability(); if (availability === Availability.downloadable) { - // Triggers async model download. + // Triggers async model download so it'll be available next time. this.download(); } if (this.mode === 'only_on_device') { From 69f393eb060b455a951c34be6621e721578525e8 Mon Sep 17 00:00:00 2001 From: Erik Eldridge Date: Tue, 22 Apr 2025 18:11:34 -0700 Subject: [PATCH 3/3] Throw Vertex error --- e2e/sample-apps/modular.js | 2 +- .../vertexai/src/methods/chrome-adapter.test.ts | 15 ++++++++++++++- packages/vertexai/src/methods/chrome-adapter.ts | 12 +++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/e2e/sample-apps/modular.js b/e2e/sample-apps/modular.js index f8b2295768a..aeebe19a4b1 100644 --- a/e2e/sample-apps/modular.js +++ b/e2e/sample-apps/modular.js @@ -314,7 +314,7 @@ async function callVertexAI(app) { console.log('[VERTEXAI] start'); const vertexAI = getVertexAI(app); const model = getGenerativeModel(vertexAI, { - mode: 'prefer_on_device' + mode: 'only_on_device' }); const singleResult = await model.generateContent([ { text: 'describe the following:' }, diff --git a/packages/vertexai/src/methods/chrome-adapter.test.ts b/packages/vertexai/src/methods/chrome-adapter.test.ts index d68b74c3a8e..7c671d63f30 100644 --- a/packages/vertexai/src/methods/chrome-adapter.test.ts +++ b/packages/vertexai/src/methods/chrome-adapter.test.ts @@ -194,7 +194,20 @@ describe('ChromeAdapter', () => { ).to.be.false; }); }); - describe('generateContentOnDevice', () => { + describe('generateContent', () => { + it('throws if Chrome API is undefined', async () => { + const adapter = new ChromeAdapter(undefined, 'only_on_device'); + await expect( + adapter.generateContent({ + contents: [] + }) + ) + .to.eventually.be.rejectedWith( + VertexAIError, + 'Chrome AI requested for unsupported browser version.' + ) + .and.have.property('code', VertexAIErrorCode.REQUEST_ERROR); + }); it('generates content', async () => { const languageModelProvider = { create: () => Promise.resolve({}) diff --git a/packages/vertexai/src/methods/chrome-adapter.ts b/packages/vertexai/src/methods/chrome-adapter.ts index 0918369384a..2490508889f 100644 --- a/packages/vertexai/src/methods/chrome-adapter.ts +++ b/packages/vertexai/src/methods/chrome-adapter.ts @@ -63,14 +63,18 @@ export class ChromeAdapter { if (this.mode === 'only_in_cloud') { return false; } + const availability = await this.languageModelProvider?.availability(); + + // Triggers async model download so it'll be available next time. if (availability === Availability.downloadable) { - // Triggers async model download so it'll be available next time. this.download(); } + if (this.mode === 'only_on_device') { return true; } + // Applies prefer_on_device logic. return ( availability === Availability.available && @@ -214,6 +218,12 @@ export class ChromeAdapter { // TODO: define a default value, since these are optional. options: LanguageModelCreateOptions ): Promise { + if (!this.languageModelProvider) { + throw new VertexAIError( + VertexAIErrorCode.REQUEST_ERROR, + 'Chrome AI requested for unsupported browser version.' + ); + } // TODO: could we use this.onDeviceParams instead of passing in options? ChromeAdapter.addImageTypeAsExpectedInput(options); const newSession = await this.languageModelProvider!.create(options);