Skip to content

Commit ee4d3aa

Browse files
committed
Test model download logic
1 parent 3f02db0 commit ee4d3aa

File tree

2 files changed

+100
-9
lines changed

2 files changed

+100
-9
lines changed

packages/vertexai/src/methods/chrome-adapter.test.ts

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { expect, use } from 'chai';
1919
import sinonChai from 'sinon-chai';
2020
import chaiAsPromised from 'chai-as-promised';
2121
import { ChromeAdapter } from './chrome-adapter';
22+
import { stub } from 'sinon';
2223

2324
use(sinonChai);
2425
use(chaiAsPromised);
@@ -41,13 +42,13 @@ describe('ChromeAdapter', () => {
4142
});
4243
});
4344
describe('isAvailable', () => {
44-
it('returns true if a model is available', async () => {
45+
it('returns true if model is readily available', async () => {
4546
const aiProvider = {
4647
languageModel: {
4748
capabilities: () =>
4849
Promise.resolve({
4950
available: 'readily'
50-
} as AILanguageModelCapabilities)
51+
})
5152
}
5253
} as AI;
5354
const adapter = new ChromeAdapter(aiProvider, 'prefer_on_device');
@@ -57,5 +58,94 @@ describe('ChromeAdapter', () => {
5758
})
5859
).to.be.true;
5960
});
61+
it('returns false and triggers download when model is available after download', async () => {
62+
const aiProvider = {
63+
languageModel: {
64+
capabilities: () =>
65+
Promise.resolve({
66+
available: 'after-download'
67+
}),
68+
create: () => Promise.resolve({})
69+
}
70+
} as AI;
71+
const createStub = stub(aiProvider.languageModel, 'create').resolves(
72+
{} as AILanguageModel
73+
);
74+
const adapter = new ChromeAdapter(aiProvider, 'prefer_on_device');
75+
expect(
76+
await adapter.isAvailable({
77+
contents: [{ role: 'user', parts: [{ text: 'hi' }] }]
78+
})
79+
).to.be.false;
80+
expect(createStub).to.have.been.calledOnce;
81+
});
82+
it('avoids redundant downloads', async () => {
83+
const aiProvider = {
84+
languageModel: {
85+
capabilities: () =>
86+
Promise.resolve({
87+
available: 'after-download'
88+
}),
89+
create: () => {}
90+
}
91+
} as AI;
92+
const downloadPromise = new Promise<AILanguageModel>(() => {
93+
/* never resolves */
94+
});
95+
const createStub = stub(aiProvider.languageModel, 'create').returns(
96+
downloadPromise
97+
);
98+
const adapter = new ChromeAdapter(aiProvider);
99+
await adapter.isAvailable({
100+
contents: [{ role: 'user', parts: [{ text: 'hi' }] }]
101+
});
102+
await adapter.isAvailable({
103+
contents: [{ role: 'user', parts: [{ text: 'hi' }] }]
104+
});
105+
expect(createStub).to.have.been.calledOnce;
106+
});
107+
it('clears state when download completes', async () => {
108+
const aiProvider = {
109+
languageModel: {
110+
capabilities: () =>
111+
Promise.resolve({
112+
available: 'after-download'
113+
}),
114+
create: () => {}
115+
}
116+
} as AI;
117+
let resolveDownload;
118+
const downloadPromise = new Promise<AILanguageModel>(resolveCallback => {
119+
resolveDownload = resolveCallback;
120+
});
121+
const createStub = stub(aiProvider.languageModel, 'create').returns(
122+
downloadPromise
123+
);
124+
const adapter = new ChromeAdapter(aiProvider);
125+
await adapter.isAvailable({
126+
contents: [{ role: 'user', parts: [{ text: 'hi' }] }]
127+
});
128+
resolveDownload!();
129+
await adapter.isAvailable({
130+
contents: [{ role: 'user', parts: [{ text: 'hi' }] }]
131+
});
132+
expect(createStub).to.have.been.calledTwice;
133+
});
134+
it('returns false when model is never available', async () => {
135+
const aiProvider = {
136+
languageModel: {
137+
capabilities: () =>
138+
Promise.resolve({
139+
available: 'no'
140+
})
141+
}
142+
} as AI;
143+
const adapter = new ChromeAdapter(aiProvider, 'prefer_on_device');
144+
expect(
145+
await adapter.isAvailable({
146+
contents: [{ role: 'user', parts: [{ text: 'hi' }] }]
147+
})
148+
).to.be.false;
149+
});
60150
});
61151
});

packages/vertexai/src/methods/chrome-adapter.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ import {
3131
* and encapsulates logic for detecting when on-device is possible.
3232
*/
3333
export class ChromeAdapter {
34-
downloadPromise: Promise<AILanguageModel> | undefined;
35-
oldSession: AILanguageModel | undefined;
34+
private isDownloading = false;
35+
private downloadPromise: Promise<AILanguageModel | void> | undefined;
36+
private oldSession: AILanguageModel | undefined;
3637
constructor(
3738
private aiProvider?: AI,
39+
// TODO: mode can be required now.
3840
private mode?: InferenceMode,
3941
private onDeviceParams?: AILanguageModelCreateOptionsWithSystemPrompt
4042
) {}
@@ -141,16 +143,15 @@ export class ChromeAdapter {
141143
.then((c: AILanguageModelCapabilities) => c.available);
142144
}
143145
private download(): void {
144-
if (this.downloadPromise) {
146+
if (this.isDownloading) {
145147
return;
146148
}
149+
this.isDownloading = true;
147150
this.downloadPromise = this.aiProvider?.languageModel
148151
.create(this.onDeviceParams)
149-
.then((model: AILanguageModel) => {
150-
delete this.downloadPromise;
151-
return model;
152+
.then(() => {
153+
this.isDownloading = false;
152154
});
153-
return;
154155
}
155156
private static toSystemPrompt(
156157
prompt: string | Content | Part | undefined

0 commit comments

Comments
 (0)