Skip to content

Commit c0d503b

Browse files
feat: allow requests to generativelanguage endpoint
This commit allows users to opt-in to using the endpoint for Vertex AI requests, instead of the default endpoint. This change involved: - Adding a new configuration option to . - Adding to the initialization of - Modifying to use the new option. - Adding the and functions. - Adding the and functions. - Adding new types for the API. - Adding unit tests to verify the new functionality.
1 parent 42cea48 commit c0d503b

File tree

7 files changed

+296
-4
lines changed

7 files changed

+296
-4
lines changed

packages/vertexai/src/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ export function getVertexAI(
5858
const vertexProvider: Provider<'vertexAI'> = _getProvider(app, VERTEX_TYPE);
5959

6060
return vertexProvider.getImmediate({
61-
identifier: options?.location || DEFAULT_LOCATION
61+
identifier: options?.location || DEFAULT_LOCATION,
62+
optionalArguments: options
6263
});
6364
}
6465

packages/vertexai/src/requests/request.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class RequestUrl {
4545
toString(): string {
4646
// TODO: allow user-set option if that feature becomes available
4747
const apiVersion = DEFAULT_API_VERSION;
48-
const baseUrl = this.requestOptions?.baseUrl || DEFAULT_BASE_URL;
48+
const baseUrl = this.requestOptions?.baseUrl || this.requestOptions?.vertexAiEndpoint || DEFAULT_BASE_URL;
4949
let url = `${baseUrl}/${apiVersion}`;
5050
url += `/projects/${this.apiSettings.project}`;
5151
url += `/locations/${this.apiSettings.location}`;
@@ -137,12 +137,16 @@ export async function makeRequest(
137137
let response;
138138
let fetchTimeoutId: string | number | NodeJS.Timeout | undefined;
139139
try {
140+
let translatedBody = body;
141+
if(requestOptions?.vertexAiEndpoint === 'generativelanguage.googleapis.com'){
142+
translatedBody = JSON.stringify(translateRequest(JSON.parse(body)));
143+
}
140144
const request = await constructRequest(
141145
model,
142146
task,
143147
apiSettings,
144148
stream,
145-
body,
149+
translatedBody,
146150
requestOptions
147151
);
148152
// Timeout is 180s by default
@@ -158,8 +162,12 @@ export async function makeRequest(
158162
if (!response.ok) {
159163
let message = '';
160164
let errorDetails;
165+
let translatedResponse = await response.json()
166+
if(requestOptions?.vertexAiEndpoint === 'generativelanguage.googleapis.com'){
167+
translatedResponse = translateResponse(translatedResponse)
168+
}
161169
try {
162-
const json = await response.json();
170+
const json = translatedResponse.error ? translatedResponse : {candidates: [translatedResponse]};
163171
message = json.error.message;
164172
if (json.error.details) {
165173
message += ` ${JSON.stringify(json.error.details)}`;

packages/vertexai/src/service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class VertexAIService implements VertexAI, _FirebaseService {
3232
auth: FirebaseAuthInternal | null;
3333
appCheck: FirebaseAppCheckInternal | null;
3434
location: string;
35+
vertexAiEndpoint?: string;
3536

3637
constructor(
3738
public app: FirebaseApp,
@@ -44,6 +45,7 @@ export class VertexAIService implements VertexAI, _FirebaseService {
4445
this.auth = auth || null;
4546
this.appCheck = appCheck || null;
4647
this.location = this.options?.location || DEFAULT_LOCATION;
48+
this.vertexAiEndpoint = this.options?.vertexAiEndpoint;
4749
}
4850

4951
_delete(): Promise<void> {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Content, Part } from '../types/content';
19+
20+
/**
21+
* This is a function that translates a request to the
22+
* `firebasevertexai` format into a request to the `generativelanguage`
23+
* format.
24+
*/
25+
import { Content, Part } from '../types/content';
26+
import * as GenerativeLanguage from '../types/generativelanguage';
27+
28+
/**
29+
* This is a function that translates a request to the
30+
* `firebasevertexai` format into a request to the `generativelanguage`
31+
* format.
32+
*/
33+
export function translateRequest(
34+
request: any
35+
): GenerativeLanguage.GenerateContentRequest {
36+
const translatedContents = request.contents.map(translateContent);
37+
return {
38+
contents: translatedContents
39+
};
40+
}
41+
42+
/**
43+
* This is a function that translates a response from the
44+
* `generativelanguage` format into a response in the
45+
* `firebasevertexai` format.
46+
*/
47+
import { Content, Part } from '../types/content';
48+
import * as GenerativeLanguage from '../types/generativelanguage';
49+
import { GenerateContentResponse } from '../types';
50+
51+
/**
52+
* This is a function that translates a request to the
53+
* `firebasevertexai` format into a request to the `generativelanguage`
54+
* format.
55+
*/
56+
export function translateRequest(
57+
request: any
58+
): GenerativeLanguage.GenerateContentRequest {
59+
const translatedContents = request.contents.map(translateContent);
60+
return {
61+
contents: translatedContents
62+
};
63+
}
64+
65+
/**
66+
* This is a function that translates a response from the
67+
* `generativelanguage` format into a response in the
68+
* `firebasevertexai` format.
69+
*/
70+
export function translateResponse(
71+
response: any
72+
): GenerateContentResponse {
73+
const translatedResponse = {
74+
candidates: [{
75+
content: {
76+
role: response[0].candidates[0].content.role,
77+
parts: response[0].candidates[0].content.parts.map(translatePart)
78+
},
79+
finishReason: response[0].candidates[0].finishReason,
80+
safetyRatings: response[0].candidates[0].safetyRatings,
81+
}]
82+
};
83+
84+
return translatedResponse;
85+
}
86+
87+
/**
88+
* This is a function that translates a content to the
89+
* `generativelanguage` format into a content in the
90+
* `firebasevertexai` format.
91+
*/
92+
export function translateContent(
93+
content: Content | Part
94+
): GenerativeLanguage.Content {
95+
const newContent = {
96+
role: content.role,
97+
parts: content.parts.map(translatePart)
98+
}
99+
return newContent;
100+
}
101+
102+
/**
103+
* This is a function that translates a part to the
104+
* `generativelanguage` format into a part in the
105+
* `firebasevertexai` format.
106+
*/
107+
export function translatePart(
108+
part: Part
109+
): GenerativeLanguage.Part {
110+
const newPart = {
111+
text: part.text,
112+
inlineData: part.inlineData
113+
}
114+
return newPart;
115+
}
116+
117+
/**
118+
* This is a function that translates a content to the
119+
* `generativelanguage` format into a content in the
120+
* `firebasevertexai` format.
121+
*/
122+
export function translateContent(
123+
content: Content | Part
124+
): GenerativeLanguage.Content {
125+
const newContent = {
126+
role: content.role,
127+
parts: content.parts.map(translatePart)
128+
}
129+
return newContent;
130+
}
131+
132+
/**
133+
* This is a function that translates a part to the
134+
* `generativelanguage` format into a part in the
135+
* `firebasevertexai` format.
136+
*/
137+
export function translatePart(
138+
part: Part
139+
): GenerativeLanguage.Part {
140+
const newPart = {
141+
text: part.text,
142+
inlineData: part.inlineData
143+
}
144+
return newPart;
145+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
export interface GenerateContentRequest {
19+
contents: Content[];
20+
}
21+
22+
export interface Content {
23+
role: string;
24+
parts: Part[];
25+
}
26+
27+
export interface Part {
28+
text?: string;
29+
inlineData?: {
30+
mimeType: string;
31+
data: string;
32+
};
33+
}

packages/vertexai/src/types/requests.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ export interface RequestOptions {
129129
* Base url for endpoint. Defaults to https://firebasevertexai.googleapis.com
130130
*/
131131
baseUrl?: string;
132+
/**
133+
* If set, will use the new `generativelanguage` endpoint instead of `firebasevertexai`
134+
* Should be one of `firebasevertexai.googleapis.com` or `generativelanguage.googleapis.com`
135+
*
136+
* @remarks This setting is used to change the Vertex AI API endpoint.
137+
*/
138+
vertexAiEndpoint?: string;
132139
}
133140

134141
/**
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { translateRequest, translateResponse, translateContent, translatePart } from '../src/translation';
19+
import { GenerativeModel } from '../src/models';
20+
import { VertexAI } from '../src/public-types';
21+
import { RequestOptions } from '../src/types';
22+
import { getVertexAI } from '../src/api';
23+
import { FirebaseApp } from '@firebase/app';
24+
import { initializeApp } from '@firebase/app';
25+
import * as assert from 'assert';
26+
27+
describe('VertexAI translation tests', () => {
28+
let app: FirebaseApp;
29+
let vertex: VertexAI;
30+
let options: RequestOptions;
31+
let model: GenerativeModel;
32+
beforeEach(() => {
33+
app = initializeApp({projectId:'test-project'});
34+
vertex = getVertexAI(app, { location: 'us-central1' });
35+
options = {vertexAiEndpoint : 'generativelanguage.googleapis.com', baseUrl:'https://test.googleapis.com/'}
36+
model = new GenerativeModel(vertex, {model:'gemini-pro'}, options);
37+
});
38+
it('should use generativelanguage as endpoint', async () => {
39+
const request = {
40+
contents: [{
41+
role: 'user',
42+
parts: [{ text: 'hello' }]
43+
}]
44+
};
45+
const translated = translateRequest(request);
46+
const response = await model.generateContent(translated);
47+
48+
const expectedResponse = {
49+
candidates:[{
50+
content: {
51+
role: 'user',
52+
parts: [{ text: 'hello' }]
53+
},
54+
finishReason: 'STOP'
55+
}],
56+
};
57+
58+
it('should use the default url if no endpoint is specified', async () => {
59+
const requestOptions: RequestOptions = {baseUrl:'https://test.googleapis.com/'};
60+
const service = new VertexAIService(app);
61+
let testurl = new URL("https://test.googleapis.com/");
62+
let defaulturl = new URL("https://firebasevertexai.googleapis.com/");
63+
const url1 = new RequestUrl('gemini-pro', 'generateContent', {project: 'test-project', location:'us-central1', apiKey:'test-key'}, false, requestOptions);
64+
assert.strictEqual(url1.toString(), testurl.toString());
65+
const url2 = new RequestUrl('gemini-pro', 'generateContent', {project: 'test-project', location:'us-central1', apiKey:'test-key'}, false, {});
66+
it('should use the default url if no endpoint is specified', async () => {
67+
const requestOptions: RequestOptions = {baseUrl:'https://test.googleapis.com/'};
68+
const service = new VertexAIService(app);
69+
let testurl = new URL("https://test.googleapis.com/");
70+
let defaulturl = new URL("https://firebasevertexai.googleapis.com/");
71+
const url1 = new RequestUrl('gemini-pro', 'generateContent', {project: 'test-project', location:'us-central1', apiKey:'test-key'}, false, requestOptions);
72+
assert.strictEqual(url1.toString(), testurl.toString());
73+
const url2 = new RequestUrl('gemini-pro', 'generateContent', {project: 'test-project', location:'us-central1', apiKey:'test-key'}, false, {});
74+
assert.strictEqual(url2.toString(), defaulturl.toString());
75+
});
76+
it('should translate response correctly', () => {
77+
const testResponse = {
78+
response: {
79+
candidates: [
80+
{
81+
content: {
82+
role: 'user',
83+
parts: [{ text: 'hello' }],
84+
},
85+
safetyRatings: [],
86+
finishReason: 'STOP',
87+
},
88+
]
89+
}
90+
};
91+
const translated = translateResponse(testResponse);
92+
assert.strictEqual(translated.candidates[0].content.role, 'user');
93+
assert.strictEqual(translated.candidates[0].content.parts[0].text, 'hello');
94+
assert.strictEqual(translated.candidates[0].finishReason, 'STOP');
95+
});
96+
});

0 commit comments

Comments
 (0)