Skip to content

Commit 569a408

Browse files
committed
Integrate chrome adapter into stream methods
1 parent 29d399f commit 569a408

File tree

5 files changed

+87
-35
lines changed

5 files changed

+87
-35
lines changed

e2e/sample-apps/modular.js

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@ import {
5858
onValue,
5959
off
6060
} from 'firebase/database';
61-
import {
62-
getGenerativeModel,
63-
getVertexAI,
64-
InferenceMode,
65-
VertexAI
66-
} from 'firebase/vertexai';
61+
import { getGenerativeModel, getVertexAI, VertexAI } from 'firebase/vertexai';
6762
import { getDataConnect, DataConnect } from 'firebase/data-connect';
6863

6964
/**
@@ -318,8 +313,13 @@ function callPerformance(app) {
318313
async function callVertexAI(app) {
319314
console.log('[VERTEXAI] start');
320315
const vertexAI = getVertexAI(app);
321-
const model = getGenerativeModel(vertexAI, { model: 'gemini-1.5-flash' });
322-
const result = await model.countTokens('abcdefg');
316+
const model = getGenerativeModel(vertexAI, {
317+
mode: 'prefer_in_cloud'
318+
});
319+
const result = await model.generateContentStream("What is Roko's Basalisk?");
320+
for await (const chunk of result.stream) {
321+
console.log(chunk.text());
322+
}
323323
console.log(`[VERTEXAI] counted tokens: ${result.totalTokens}`);
324324
}
325325

@@ -337,17 +337,6 @@ function callDataConnect(app) {
337337
console.log('[DATACONNECT] initialized');
338338
}
339339

340-
async function callVertex(app) {
341-
console.log('[VERTEX] start');
342-
const vertex = getVertexAI(app);
343-
const model = getGenerativeModel(vertex, {
344-
mode: InferenceMode.PREFER_ON_DEVICE
345-
});
346-
const result = await model.generateContent("What is Roko's Basalisk?");
347-
console.log(result.response.text());
348-
console.log('[VERTEX] initialized');
349-
}
350-
351340
/**
352341
* Run smoke tests for all products.
353342
* Comment out any products you want to ignore.
@@ -357,19 +346,18 @@ async function main() {
357346
const app = initializeApp(config);
358347
setLogLevel('warn');
359348

360-
callAppCheck(app);
361-
await authLogin(app);
362-
await callStorage(app);
363-
await callFirestore(app);
364-
await callDatabase(app);
365-
await callMessaging(app);
366-
callAnalytics(app);
367-
callPerformance(app);
368-
await callFunctions(app);
349+
// callAppCheck(app);
350+
// await authLogin(app);
351+
// await callStorage(app);
352+
// await callFirestore(app);
353+
// await callDatabase(app);
354+
// await callMessaging(app);
355+
// callAnalytics(app);
356+
// callPerformance(app);
357+
// await callFunctions(app);
369358
await callVertexAI(app);
370-
callDataConnect(app);
371-
await authLogout(app);
372-
await callVertex(app);
359+
// callDataConnect(app);
360+
// await authLogout(app);
373361
console.log('DONE');
374362
}
375363

packages/vertexai/src/methods/chat-session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export class ChatSession {
149149
this._apiSettings,
150150
this.model,
151151
generateContentRequest,
152+
this.chromeAdapter,
152153
this.requestOptions
153154
);
154155

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ export class ChromeAdapter {
7878
): Promise<EnhancedGenerateContentResponse> {
7979
const createOptions = this.onDeviceParams || {};
8080
createOptions.initialPrompts ??= [];
81-
const extractedInitialPrompts = ChromeAdapter.toInitialPrompts(request.contents);
81+
const extractedInitialPrompts = ChromeAdapter.toInitialPrompts(
82+
request.contents
83+
);
8284
// Assumes validation asserted there is at least one initial prompt.
8385
const prompt = extractedInitialPrompts.pop()!;
8486
createOptions.initialPrompts.push(...extractedInitialPrompts);
@@ -89,6 +91,46 @@ export class ChromeAdapter {
8991
functionCalls: () => undefined
9092
};
9193
}
94+
async generateContentStreamOnDevice(
95+
request: GenerateContentRequest
96+
): Promise<Response> {
97+
const createOptions = this.onDeviceParams || {};
98+
createOptions.initialPrompts ??= [];
99+
const extractedInitialPrompts = ChromeAdapter.toInitialPrompts(
100+
request.contents
101+
);
102+
// Assumes validation asserted there is at least one initial prompt.
103+
const prompt = extractedInitialPrompts.pop()!;
104+
createOptions.initialPrompts.push(...extractedInitialPrompts);
105+
const session = await this.session(createOptions);
106+
const stream = await session.promptStreaming(prompt.content);
107+
return ChromeAdapter.toStreamResponse(stream);
108+
}
109+
// Formats string stream returned by Chrome as SSE returned by Vertex.
110+
private static async toStreamResponse(
111+
stream: ReadableStream<string>
112+
): Promise<Response> {
113+
const encoder = new TextEncoder();
114+
return {
115+
body: stream.pipeThrough(
116+
new TransformStream({
117+
transform(chunk, controller) {
118+
const json = JSON.stringify({
119+
candidates: [
120+
{
121+
content: {
122+
role: 'model',
123+
parts: [{ text: chunk }]
124+
}
125+
}
126+
]
127+
});
128+
controller.enqueue(encoder.encode(`data: ${json}\n\n`));
129+
}
130+
})
131+
)
132+
} as Response;
133+
}
92134
private static isOnDeviceRequest(request: GenerateContentRequest): boolean {
93135
// Returns false if the prompt is empty.
94136
if (request.contents.length === 0) {

packages/vertexai/src/methods/generate-content.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,40 @@ import { processStream } from '../requests/stream-reader';
2929
import { ApiSettings } from '../types/internal';
3030
import { ChromeAdapter } from './chrome-adapter';
3131

32-
export async function generateContentStream(
32+
async function generateContentStreamOnCloud(
3333
apiSettings: ApiSettings,
3434
model: string,
3535
params: GenerateContentRequest,
3636
requestOptions?: RequestOptions
37-
): Promise<GenerateContentStreamResult> {
38-
const response = await makeRequest(
37+
): Promise<Response> {
38+
return makeRequest(
3939
model,
4040
Task.STREAM_GENERATE_CONTENT,
4141
apiSettings,
4242
/* stream */ true,
4343
JSON.stringify(params),
4444
requestOptions
4545
);
46+
}
47+
48+
export async function generateContentStream(
49+
apiSettings: ApiSettings,
50+
model: string,
51+
params: GenerateContentRequest,
52+
chromeAdapter: ChromeAdapter,
53+
requestOptions?: RequestOptions
54+
): Promise<GenerateContentStreamResult> {
55+
let response;
56+
if (await chromeAdapter.isAvailable(params)) {
57+
response = await chromeAdapter.generateContentStreamOnDevice(params);
58+
} else {
59+
response = await generateContentStreamOnCloud(
60+
apiSettings,
61+
model,
62+
params,
63+
requestOptions
64+
);
65+
}
4666
return processStream(response);
4767
}
4868

packages/vertexai/src/models/generative-model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export class GenerativeModel extends VertexAIModel {
123123
systemInstruction: this.systemInstruction,
124124
...formattedParams
125125
},
126+
this.chromeAdapter,
126127
this.requestOptions
127128
);
128129
}

0 commit comments

Comments
 (0)