Skip to content

Commit 7da51a5

Browse files
committed
wip
1 parent 59ae45e commit 7da51a5

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

common/api-review/vertexai.api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,11 @@ export class GenerativeModel {
337337
// (undocumented)
338338
safetySettings: SafetySetting[];
339339
startChat(startChatParams?: StartChatParams): ChatSession;
340+
// Warning: (ae-forgotten-export) The symbol "LiveGenerationConfig" needs to be exported by the entry point index.d.ts
341+
// Warning: (ae-forgotten-export) The symbol "LiveSession" needs to be exported by the entry point index.d.ts
342+
//
343+
// (undocumented)
344+
startLiveSession(config?: LiveGenerationConfig): Promise<LiveSession>;
340345
// (undocumented)
341346
systemInstruction?: Content;
342347
// (undocumented)

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { VertexAI } from '../public-types';
4747
import { ApiSettings } from '../types/internal';
4848
import { VertexAIService } from '../service';
4949
import { _isFirebaseServerApp } from '@firebase/app';
50+
import { LiveClientContent, LiveClientSetup, LiveGenerationConfig, LiveServerContent } from '../types/live';
5051

5152
/**
5253
* Class for generative model APIs.
@@ -190,6 +191,55 @@ export class GenerativeModel {
190191
);
191192
}
192193

194+
async startLiveSession(config?: LiveGenerationConfig): Promise<LiveSession> {
195+
const _bidiGoogleAI = true;
196+
const _baseDailyUrl = 'daily-firebaseml.sandbox.googleapis.com';
197+
const _apiUrl =
198+
'ws/google.firebase.machinelearning.v2beta.LlmBidiService/BidiGenerateContent?key=';
199+
const _baseGAIUrl = 'generativelanguage.googleapis.com';
200+
const _apiGAIUrl = 'ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=';
201+
const model = 'gemini-2.0-flash-exp'
202+
203+
let url;
204+
let modelString = '';
205+
if (_bidiGoogleAI) {
206+
const gaiApiKey = '';
207+
url = `wss://${_baseGAIUrl}/${_apiGAIUrl}${gaiApiKey}`;
208+
modelString = `models/${model}`;
209+
} else {
210+
url = `wss://${_baseDailyUrl}/${_apiUrl}${this._apiSettings.apiKey}`;
211+
modelString =
212+
`projects/${this._apiSettings.project}/locations/${this._apiSettings.location}/publishers/google/models/${model}`;
213+
}
214+
215+
const socket = new WebSocket(url)
216+
217+
socket.onopen = () => {
218+
const liveClientSetup: LiveClientSetup = {
219+
setup: {
220+
model: modelString,
221+
generation_config: config
222+
}
223+
}
224+
socket.send(JSON.stringify(liveClientSetup));
225+
}
226+
227+
const setupComplete = new Promise((resolve, reject) => {
228+
socket.onmessage = async (event) => {
229+
console.log('received message in `startLiveSession`')
230+
const msg = JSON.parse(await (event.data as Blob).text());
231+
if (msg.setupComplete) {
232+
resolve('setup complete.');
233+
} else {
234+
reject('first message did not contain `setup_complete`');
235+
}
236+
};
237+
});
238+
239+
await setupComplete;
240+
return new LiveSession(socket);
241+
}
242+
193243
/**
194244
* Counts the tokens in the provided request.
195245
*/
@@ -200,3 +250,44 @@ export class GenerativeModel {
200250
return countTokens(this._apiSettings, this.model, formattedParams);
201251
}
202252
}
253+
254+
export class LiveSession {
255+
constructor(private socket: WebSocket) {
256+
console.log('started new LiveSession');
257+
this.socket.onclose = (event) => {
258+
console.log('websocket closed', event);
259+
}
260+
261+
this.socket.onerror = (event) => {
262+
console.log('websocket error:', event)
263+
}
264+
}
265+
266+
send(data: string, turnComplete: boolean) {
267+
if(!this.socket.OPEN) {
268+
throw new Error("Cannot send message. Live connection was closed.")
269+
}
270+
const msg: LiveClientContent = {
271+
client_content: {
272+
turns: [{
273+
role: 'user',
274+
parts: [{
275+
text: data
276+
}]
277+
}],
278+
turn_complete: turnComplete
279+
},
280+
}
281+
this.socket.send(JSON.stringify(msg));
282+
}
283+
284+
// Assumes the setup_complete message was already received
285+
onMessage(callback: (content: LiveServerContent) => void) {
286+
console.log("setting onMessage callback");
287+
this.socket.onmessage = async (event) => {
288+
console.log("triggering onMessage callback");
289+
const content: LiveServerContent = JSON.parse(await (event.data as Blob).text())
290+
callback(content);
291+
}
292+
}
293+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// sent in the first client message after establishing connection.
2+
export interface LiveClientSetup {
3+
setup: {
4+
model: string;
5+
generation_config?: LiveGenerationConfig
6+
}
7+
}
8+
9+
export interface LiveGenerationConfig {
10+
response_modalities: string[];
11+
speech_config: {
12+
voice_config: {
13+
prebuilt_voice_config: {
14+
voice_name: string;
15+
}
16+
}
17+
}
18+
}
19+
20+
// response from the server after setup.
21+
export interface LiveServerContent {
22+
serverContent: {
23+
// Defined if turn not complete
24+
modelTurn?: {
25+
parts: {
26+
inlineData: {
27+
mimeType: string,
28+
data: string
29+
}
30+
}[]
31+
};
32+
// Defined if turn complete
33+
turnComplete?: boolean
34+
};
35+
}
36+
37+
// user input sent in real time.
38+
export interface LiveClientRealtimeInput {
39+
mediaChunks: {
40+
mime_type: string;
41+
data: Uint8Array,
42+
}
43+
}
44+
45+
export interface LiveClientContent {
46+
client_content: {
47+
turns: {
48+
role: string;
49+
parts: {
50+
text: string
51+
}[];
52+
}[];
53+
turn_complete: boolean;
54+
}
55+
}

0 commit comments

Comments
 (0)