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);