Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/core/examples/external_clients/google_vertex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";

/**
* Example of using Google Vertex AI directly (not through AI SDK).
* When you provide `vertexai: true` in the client options,
* the system will route to GoogleVertexClient instead of using AI SDK.
*/
async function main() {
const stagehand = new Stagehand({
env: "LOCAL",
modelName: "google/gemini-1.5-pro", // Google model with slash notation
modelClientOptions: {
// Vertex AI specific configuration - bypasses AI SDK
vertexai: true,
project: "your-gcp-project-id",
location: "us-central1",
// Optional: API key if not using default auth
// apiKey: process.env.GOOGLE_API_KEY,
},
});

await stagehand.init();
const page = stagehand.context.pages()[0];
await page.goto("https://docs.stagehand.dev");

// Extract some text using Vertex AI (not AI SDK)
const result = await stagehand.extract("extract the main heading of this page", z.object({
heading: z.string(),
}));

console.log("Extracted:", result);
await stagehand.close();
}

main().catch(console.error);
23 changes: 16 additions & 7 deletions packages/core/lib/v3/llm/GoogleClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {
Content,
FunctionCall,
GoogleGenAI,
HarmCategory,
GoogleGenAIOptions,
HarmBlockThreshold,
Content,
HarmCategory,
Part,
Tool,
FunctionCall,
Schema,
Tool,
Type,
} from "@google/genai";

Expand Down Expand Up @@ -60,7 +61,7 @@ const safetySettings = [
export class GoogleClient extends LLMClient {
public type = "google" as const;
private client: GoogleGenAI;
public clientOptions: ClientOptions;
public clientOptions: GoogleGenAIOptions;
public hasVision: boolean;
private logger: (message: LogLine) => void;

Expand All @@ -78,8 +79,16 @@ export class GoogleClient extends LLMClient {
// Try to get the API key from the environment variable GOOGLE_API_KEY
clientOptions.apiKey = loadApiKeyFromEnv("google_legacy", logger);
}
this.clientOptions = clientOptions;
this.client = new GoogleGenAI({ apiKey: clientOptions.apiKey });
this.clientOptions = clientOptions as GoogleGenAIOptions;
this.client = new GoogleGenAI({
apiKey: this.clientOptions.apiKey,
vertexai: this.clientOptions.vertexai,
project: this.clientOptions.project,
location: this.clientOptions.location,
apiVersion: this.clientOptions.apiVersion,
googleAuthOptions: this.clientOptions.googleAuthOptions,
httpOptions: this.clientOptions.httpOptions,
});
this.modelName = modelName;
this.logger = logger;
// Determine vision capability based on model name (adjust as needed)
Expand Down
43 changes: 43 additions & 0 deletions packages/core/lib/v3/llm/GoogleVertexClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { GoogleGenAIOptions } from "@google/genai";
import type { LogLine } from "../types/public/logs";
import type { AvailableModel, ClientOptions } from "../types/public/model";
import { GoogleClient } from "./GoogleClient";

export interface GoogleVertexClientOptions extends GoogleGenAIOptions {
vertexai: boolean;
project: string;
location: string;
}

export class GoogleVertexClient extends GoogleClient {
constructor({
logger,
modelName,
clientOptions,
}: {
logger: (message: LogLine) => void;
modelName: AvailableModel;
clientOptions?: ClientOptions;
}) {
// Ensure vertex ai configuration is present
const vertexOptions = clientOptions as GoogleVertexClientOptions;
if (!vertexOptions?.vertexai) {
throw new Error("GoogleVertexClient requires vertexai option to be true");
}
if (!vertexOptions?.project) {
throw new Error("GoogleVertexClient requires project configuration");
}
if (!vertexOptions?.location) {
throw new Error("GoogleVertexClient requires location configuration");
}

super({
logger,
modelName,
clientOptions: {
...vertexOptions,
vertexai: true,
},
});
}
}
28 changes: 28 additions & 0 deletions packages/core/lib/v3/llm/LLMProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AISdkClient } from "./aisdk";
import { AnthropicClient } from "./AnthropicClient";
import { CerebrasClient } from "./CerebrasClient";
import { GoogleClient } from "./GoogleClient";
import { GoogleVertexClient } from "./GoogleVertexClient";
import { GroqClient } from "./GroqClient";
import { LLMClient } from "./LLMClient";
import { OpenAIClient } from "./OpenAIClient";
Expand Down Expand Up @@ -44,6 +45,15 @@ const AISDKProviders: Record<string, AISDKProvider> = {
perplexity,
ollama,
};

function isVertexAIRequest(clientOptions?: ClientOptions): boolean {
return !!(
clientOptions &&
"vertexai" in clientOptions &&
clientOptions.vertexai
);
}

const AISDKProvidersWithAPIKey: Record<string, AISDKCustomProvider> = {
openai: createOpenAI,
anthropic: createAnthropic,
Expand Down Expand Up @@ -98,7 +108,15 @@ export function getAISDKLanguageModel(
subModelName: string,
apiKey?: string,
baseURL?: string,
clientOptions?: ClientOptions,
) {
// If this is a google model with vertex AI configuration, don't use AI SDK
if (subProvider === "google" && isVertexAIRequest(clientOptions)) {
throw new Error(
"Vertex AI models should use GoogleVertexClient, not AI SDK",
);
}

if (apiKey) {
const creator = AISDKProvidersWithAPIKey[subProvider];
if (!creator) {
Expand Down Expand Up @@ -143,11 +161,21 @@ export class LLMProvider {
const subProvider = modelName.substring(0, firstSlashIndex);
const subModelName = modelName.substring(firstSlashIndex + 1);

// Check if this is a vertex AI request for google models
if (subProvider === "google" && isVertexAIRequest(clientOptions)) {
return new GoogleVertexClient({
logger: this.logger,
modelName: modelName,
clientOptions,
});
}

const languageModel = getAISDKLanguageModel(
subProvider,
subModelName,
clientOptions?.apiKey,
clientOptions?.baseURL,
clientOptions,
);

return new AISdkClient({
Expand Down
6 changes: 5 additions & 1 deletion packages/core/lib/v3/types/public/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ClientOptions as AnthropicClientOptions } from "@anthropic-ai/sdk";
import type { LanguageModelV2 } from "@ai-sdk/provider";
import type { GoogleGenAIOptions as GoogleClientOptions } from "@google/genai";
import type { ClientOptions as OpenAIClientOptions } from "openai";

export type AnthropicJsonSchemaObject = {
Expand Down Expand Up @@ -66,7 +67,10 @@ export type ModelProvider =
| "google"
| "aisdk";

export type ClientOptions = OpenAIClientOptions | AnthropicClientOptions;
export type ClientOptions =
| OpenAIClientOptions
| AnthropicClientOptions
| GoogleClientOptions;

export type ModelConfiguration =
| AvailableModel
Expand Down