Skip to content

Commit 1f43050

Browse files
committed
Initial prototype
1 parent dcfb3da commit 1f43050

File tree

15 files changed

+232
-29
lines changed

15 files changed

+232
-29
lines changed

common/api-review/vertexai.api.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,8 @@ export interface UsageMetadata {
797797
export interface VertexAI {
798798
app: FirebaseApp;
799799
// (undocumented)
800+
developerAPIEnabled: boolean;
801+
// (undocumented)
800802
location: string;
801803
}
802804

@@ -831,11 +833,13 @@ export abstract class VertexAIModel {
831833
// @internal (undocumented)
832834
protected _apiSettings: ApiSettings;
833835
readonly model: string;
834-
static normalizeModelName(modelName: string): string;
836+
static normalizeModelName(modelName: string, developerAPIEnabled?: boolean): string;
835837
}
836838

837839
// @public
838840
export interface VertexAIOptions {
841+
// (undocumented)
842+
developerAPIEnabled: boolean;
839843
// (undocumented)
840844
location?: string;
841845
}

packages/vertexai/src/api.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from './types';
3030
import { VertexAIError } from './errors';
3131
import { VertexAIModel, GenerativeModel, ImagenModel } from './models';
32+
import { createInstanceIdentifier } from './helpers';
3233

3334
export { ChatSession } from './methods/chat-session';
3435
export * from './requests/schema-builder';
@@ -57,8 +58,12 @@ export function getVertexAI(
5758
// Dependencies
5859
const vertexProvider: Provider<'vertexAI'> = _getProvider(app, VERTEX_TYPE);
5960

61+
const identifier = createInstanceIdentifier(
62+
options?.developerAPIEnabled,
63+
options?.location
64+
);
6065
return vertexProvider.getImmediate({
61-
identifier: options?.location || DEFAULT_LOCATION
66+
identifier
6267
});
6368
}
6469

packages/vertexai/src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export const DEFAULT_LOCATION = 'us-central1';
2323

2424
export const DEFAULT_BASE_URL = 'https://firebasevertexai.googleapis.com';
2525

26+
export const DEVELOPER_API_BASE_URL =
27+
'https://generativelanguage.googleapis.com';
28+
2629
export const DEFAULT_API_VERSION = 'v1beta';
2730

2831
export const PACKAGE_VERSION = version;

packages/vertexai/src/helpers.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @license
3+
* Copyright 2025 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 { DEFAULT_LOCATION } from './constants';
19+
20+
/**
21+
* @internal
22+
*/
23+
export function createInstanceIdentifier(
24+
developerAPIEnabled?: boolean,
25+
location?: string
26+
): string {
27+
if (developerAPIEnabled) {
28+
return 'developerAPI';
29+
} else {
30+
return `vertexAI/${location || DEFAULT_LOCATION}`;
31+
}
32+
}
33+
34+
/**
35+
* @internal
36+
*/
37+
export function parseInstanceIdentifier(instanceIdentifier: string): {
38+
developerAPIEnabled: boolean;
39+
location?: string;
40+
} {
41+
const identifierParts = instanceIdentifier.split('/');
42+
if (identifierParts[0] === 'developerAPI') {
43+
return {
44+
developerAPIEnabled: true,
45+
location: undefined
46+
};
47+
} else {
48+
const location = identifierParts[1];
49+
return {
50+
developerAPIEnabled: false,
51+
location
52+
};
53+
}
54+
}

packages/vertexai/src/index.node.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,35 @@ import { VertexAIService } from './service';
2626
import { VERTEX_TYPE } from './constants';
2727
import { Component, ComponentType } from '@firebase/component';
2828
import { name, version } from '../package.json';
29+
import { VertexAIOptions } from './public-types';
30+
import { parseInstanceIdentifier } from './helpers';
2931

3032
function registerVertex(): void {
3133
_registerComponent(
3234
new Component(
3335
VERTEX_TYPE,
34-
(container, { instanceIdentifier: location }) => {
36+
(container, options) => {
3537
// getImmediate for FirebaseApp will always succeed
3638
const app = container.getProvider('app').getImmediate();
3739
const auth = container.getProvider('auth-internal');
3840
const appCheckProvider = container.getProvider('app-check-internal');
39-
return new VertexAIService(app, auth, appCheckProvider, { location });
41+
42+
let vertexAIOptions: VertexAIOptions;
43+
if (options.instanceIdentifier) {
44+
vertexAIOptions = parseInstanceIdentifier(options.instanceIdentifier);
45+
} else {
46+
vertexAIOptions = {
47+
developerAPIEnabled: false,
48+
location: undefined
49+
};
50+
}
51+
52+
return new VertexAIService(
53+
app,
54+
auth,
55+
appCheckProvider,
56+
vertexAIOptions
57+
);
4058
},
4159
ComponentType.PUBLIC
4260
).setMultipleInstances(true)

packages/vertexai/src/index.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@
2424
import { registerVersion, _registerComponent } from '@firebase/app';
2525
import { VertexAIService } from './service';
2626
import { VERTEX_TYPE } from './constants';
27-
import { Component, ComponentType } from '@firebase/component';
27+
import {
28+
Component,
29+
ComponentType,
30+
InstanceFactoryOptions
31+
} from '@firebase/component';
2832
import { name, version } from '../package.json';
33+
import { parseInstanceIdentifier } from './helpers';
34+
import { VertexAIOptions } from './public-types';
2935

3036
declare global {
3137
interface Window {
@@ -37,12 +43,28 @@ function registerVertex(): void {
3743
_registerComponent(
3844
new Component(
3945
VERTEX_TYPE,
40-
(container, { instanceIdentifier: location }) => {
46+
(container, options) => {
4147
// getImmediate for FirebaseApp will always succeed
4248
const app = container.getProvider('app').getImmediate();
4349
const auth = container.getProvider('auth-internal');
4450
const appCheckProvider = container.getProvider('app-check-internal');
45-
return new VertexAIService(app, auth, appCheckProvider, { location });
51+
52+
let vertexAIOptions: VertexAIOptions;
53+
if (options.instanceIdentifier) {
54+
vertexAIOptions = parseInstanceIdentifier(options.instanceIdentifier);
55+
} else {
56+
vertexAIOptions = {
57+
developerAPIEnabled: false,
58+
location: undefined
59+
};
60+
}
61+
62+
return new VertexAIService(
63+
app,
64+
auth,
65+
appCheckProvider,
66+
vertexAIOptions
67+
);
4668
},
4769
ComponentType.PUBLIC
4870
).setMultipleInstances(true)

packages/vertexai/src/mapper.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { CitationMetadata, GenerateContentCandidate, GenerateContentResponse } from "./types";
2+
import { DeveloperAPI } from "./types/developerAPI";
3+
4+
/**
5+
* Maps a `GenerateContentResponse` from the Developer API to the format of the
6+
* `GenerateContentResponse` that we get from VertexAI that is exposed in the public API.
7+
*
8+
* @internal
9+
*
10+
* @param developerAPIResponse The `GenerateContentResponse` from the Developer API.
11+
* @returns A `GenerateContentResponse` that conforms to the public API's format.
12+
*/
13+
export function mapGenerateContentResponse(developerAPIResponse: DeveloperAPI.GenerateContentResponse): GenerateContentResponse {
14+
let candidates: GenerateContentCandidate[] | undefined = developerAPIResponse.candidates ? [] : undefined;
15+
if (candidates) {
16+
developerAPIResponse.candidates?.forEach(candidate => {
17+
let citationMetadata: CitationMetadata | undefined
18+
if (candidate.citationMetadata) {
19+
citationMetadata = {
20+
citations: candidate.citationMetadata.citationSources
21+
}
22+
}
23+
const mappedCandidate = {
24+
index: candidate.index,
25+
content: candidate.content,
26+
finishReason: candidate.finishReason,
27+
finishMessage: candidate.finishMessage,
28+
safetyRatings: candidate.safetyRatings,
29+
citationMetadata,
30+
groundingMetadata: candidate.groundingMetadata
31+
};
32+
candidates.push(mappedCandidate);
33+
});
34+
}
35+
36+
const generateContentResponse = {
37+
candidates,
38+
promptFeedback: developerAPIResponse.promptFeedback,
39+
usageMetadata: developerAPIResponse.usageMetadata
40+
}
41+
return generateContentResponse;
42+
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { Task, makeRequest } from '../requests/request';
2626
import { createEnhancedContentResponse } from '../requests/response-helpers';
2727
import { processStream } from '../requests/stream-reader';
2828
import { ApiSettings } from '../types/internal';
29+
import { mapGenerateContentResponse } from '../mapper';
2930

3031
export async function generateContentStream(
3132
apiSettings: ApiSettings,
@@ -58,8 +59,13 @@ export async function generateContent(
5859
JSON.stringify(params),
5960
requestOptions
6061
);
61-
const responseJson: GenerateContentResponse = await response.json();
62-
const enhancedResponse = createEnhancedContentResponse(responseJson);
62+
const responseJson: any = await response.json();
63+
const generateContentResponse: GenerateContentResponse = apiSettings.developerAPIEnabled
64+
? mapGenerateContentResponse(responseJson)
65+
: await responseJson;
66+
const enhancedResponse = createEnhancedContentResponse(
67+
generateContentResponse
68+
);
6369
return {
6470
response: enhancedResponse
6571
};

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

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ export abstract class VertexAIModel {
5656
* @internal
5757
*/
5858
protected constructor(vertexAI: VertexAI, modelName: string) {
59-
this.model = VertexAIModel.normalizeModelName(modelName);
60-
6159
if (!vertexAI.app?.options?.apiKey) {
6260
throw new VertexAIError(
6361
VertexAIErrorCode.NO_API_KEY,
@@ -72,7 +70,8 @@ export abstract class VertexAIModel {
7270
this._apiSettings = {
7371
apiKey: vertexAI.app.options.apiKey,
7472
project: vertexAI.app.options.projectId,
75-
location: vertexAI.location
73+
location: vertexAI.location,
74+
developerAPIEnabled: vertexAI.developerAPIEnabled
7675
};
7776

7877
if (
@@ -92,6 +91,11 @@ export abstract class VertexAIModel {
9291
this._apiSettings.getAuthToken = () =>
9392
(vertexAI as VertexAIService).auth!.getToken();
9493
}
94+
95+
this.model = VertexAIModel.normalizeModelName(
96+
modelName,
97+
this._apiSettings.developerAPIEnabled
98+
);
9599
}
96100
}
97101

@@ -101,19 +105,26 @@ export abstract class VertexAIModel {
101105
* @param modelName - The model name to normalize.
102106
* @returns The fully qualified model resource name.
103107
*/
104-
static normalizeModelName(modelName: string): string {
108+
static normalizeModelName(
109+
modelName: string,
110+
developerAPIEnabled?: boolean
111+
): string {
105112
let model: string;
106-
if (modelName.includes('/')) {
107-
if (modelName.startsWith('models/')) {
108-
// Add 'publishers/google' if the user is only passing in 'models/model-name'.
109-
model = `publishers/google/${modelName}`;
113+
if (developerAPIEnabled) {
114+
model = `models/${modelName}`;
115+
} else {
116+
if (modelName.includes('/')) {
117+
if (modelName.startsWith('models/')) {
118+
// Add 'publishers/google' if the user is only passing in 'models/model-name'.
119+
model = `publishers/google/${modelName}`;
120+
} else {
121+
// Any other custom format (e.g. tuned models) must be passed in correctly.
122+
model = modelName;
123+
}
110124
} else {
111-
// Any other custom format (e.g. tuned models) must be passed in correctly.
112-
model = modelName;
125+
// If path is not included, assume it's a non-tuned model.
126+
model = `publishers/google/models/${modelName}`;
113127
}
114-
} else {
115-
// If path is not included, assume it's a non-tuned model.
116-
model = `publishers/google/models/${modelName}`;
117128
}
118129

119130
return model;

packages/vertexai/src/public-types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ export interface VertexAI {
2828
* The {@link @firebase/app#FirebaseApp} this <code>{@link VertexAI}</code> instance is associated with.
2929
*/
3030
app: FirebaseApp;
31-
location: string;
31+
developerAPIEnabled: boolean;
32+
location: string; // This is only applicable if we're using the VertexAI API.
3233
}
3334

3435
/**
3536
* Options when initializing the Vertex AI in Firebase SDK.
3637
* @public
3738
*/
3839
export interface VertexAIOptions {
40+
developerAPIEnabled: boolean;
3941
location?: string;
4042
}

0 commit comments

Comments
 (0)