Skip to content

Commit ad75ed2

Browse files
authored
Merge pull request #94 from stainless-sdks/STG-1756-vertex-auth-example
[STG-1756] Add Vertex auth example
2 parents 09bdd54 + 903f5d8 commit ad75ed2

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

examples/vertex_auth_example.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
Example: use Google Vertex AI model auth with the Stagehand Python SDK.
3+
4+
This runs the same model-backed flow against both:
5+
- server="remote" (hosted Stagehand API + Browserbase browser)
6+
- server="local" (local Stagehand server + local browser)
7+
8+
Required environment variables:
9+
- BROWSERBASE_API_KEY
10+
- GOOGLE_APPLICATION_CREDENTIALS: path to a service account JSON file
11+
OR VERTEX_SERVICE_ACCOUNT_JSON: raw service account JSON
12+
13+
Optional environment variables:
14+
- VERTEX_PROJECT: Google Cloud project ID. Defaults to credentials.project_id.
15+
- VERTEX_LOCATION: Google Cloud location. Defaults to us-central1.
16+
- VERTEX_MODEL: Vertex model name. Defaults to vertex/gemini-2.5-flash.
17+
- STAGEHAND_BOOTSTRAP_MODEL: session start model. Defaults to openai/gpt-4.1-mini.
18+
19+
The service account JSON is read by this script and sent inline as credentials.
20+
Do not pass key file paths to the Stagehand API server.
21+
"""
22+
23+
from __future__ import annotations
24+
25+
import os
26+
import sys
27+
import json
28+
from typing import Any, Literal, cast
29+
from pathlib import Path
30+
31+
from stagehand import Stagehand
32+
from stagehand.types.session_extract_params import (
33+
OptionsModelVertexModelConfigObject as VertexModelConfig,
34+
OptionsModelVertexModelConfigObjectAuthCredentials as VertexCredentials,
35+
)
36+
37+
38+
def load_example_env() -> None:
39+
env_path = Path(__file__).parent / ".env"
40+
if not env_path.exists():
41+
return
42+
43+
for line in env_path.read_text().splitlines():
44+
line = line.strip()
45+
if not line or line.startswith("#") or "=" not in line:
46+
continue
47+
key, value = line.split("=", 1)
48+
os.environ.setdefault(key, value)
49+
50+
51+
def load_vertex_credentials() -> VertexCredentials:
52+
inline_json = os.environ.get("VERTEX_SERVICE_ACCOUNT_JSON")
53+
if inline_json:
54+
credentials = json.loads(inline_json)
55+
else:
56+
credentials_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
57+
if not credentials_path:
58+
sys.exit(
59+
"Set GOOGLE_APPLICATION_CREDENTIALS to a service account JSON file "
60+
"or VERTEX_SERVICE_ACCOUNT_JSON to raw service account JSON."
61+
)
62+
credentials = json.loads(Path(credentials_path).expanduser().read_text())
63+
64+
if not isinstance(credentials, dict):
65+
sys.exit("Vertex credentials must be a JSON object.")
66+
67+
missing = [
68+
key for key in ("client_email", "private_key") if not credentials.get(key)
69+
]
70+
if missing:
71+
sys.exit("Vertex credentials are missing: " + ", ".join(missing))
72+
73+
return cast(VertexCredentials, credentials)
74+
75+
76+
def build_vertex_model_config() -> VertexModelConfig:
77+
credentials = load_vertex_credentials()
78+
project = os.environ.get("VERTEX_PROJECT") or credentials.get("project_id")
79+
if not project:
80+
sys.exit("Set VERTEX_PROJECT or include project_id in the credentials JSON.")
81+
82+
location = os.environ.get("VERTEX_LOCATION", "us-central1")
83+
model_name = os.environ.get("VERTEX_MODEL", "vertex/gemini-2.5-flash")
84+
85+
return {
86+
"provider": "vertex",
87+
"model_name": model_name,
88+
"auth": {
89+
"type": "googleServiceAccount",
90+
"credentials": credentials,
91+
},
92+
"provider_options": {
93+
"vertex": {
94+
"project": project,
95+
"location": location,
96+
}
97+
},
98+
}
99+
100+
101+
def run_vertex_flow(
102+
server: Literal["remote", "local"], vertex_model: VertexModelConfig
103+
) -> None:
104+
browserbase_api_key = os.environ.get("BROWSERBASE_API_KEY")
105+
if not browserbase_api_key:
106+
sys.exit("Set BROWSERBASE_API_KEY to run this example.")
107+
108+
client_kwargs: dict[str, Any] = {
109+
"server": server,
110+
"browserbase_api_key": browserbase_api_key,
111+
}
112+
browser: dict[str, Any]
113+
114+
if server == "local":
115+
client_kwargs["local_ready_timeout_s"] = 30.0
116+
browser = {
117+
"type": "local",
118+
"launchOptions": {
119+
"headless": True,
120+
},
121+
}
122+
else:
123+
browser = {"type": "browserbase"}
124+
125+
bootstrap_model = os.environ.get("STAGEHAND_BOOTSTRAP_MODEL", "openai/gpt-4.1-mini")
126+
127+
with Stagehand(**client_kwargs) as client:
128+
session = client.sessions.start(
129+
model_name=bootstrap_model,
130+
browser=browser,
131+
)
132+
133+
try:
134+
session.navigate(url="https://example.com")
135+
136+
observe_result = session.observe(
137+
instruction="Find the main heading on the page.",
138+
options={"model": vertex_model},
139+
)
140+
print(f"[{server}] observe:", observe_result)
141+
142+
extract_result = session.extract(
143+
instruction="Extract the page title and main heading.",
144+
schema={
145+
"type": "object",
146+
"properties": {
147+
"title": {"type": "string"},
148+
"heading": {"type": "string"},
149+
},
150+
"required": ["title", "heading"],
151+
"additionalProperties": False,
152+
},
153+
options={"model": vertex_model},
154+
)
155+
print(f"[{server}] extract:", extract_result)
156+
157+
execute_result = session.execute(
158+
agent_config={
159+
"model": vertex_model,
160+
"cua": False,
161+
},
162+
execute_options={
163+
"instruction": "Summarize the current page in one sentence.",
164+
"max_steps": 3,
165+
},
166+
)
167+
print(f"[{server}] execute:", execute_result)
168+
finally:
169+
session.end()
170+
171+
172+
def main() -> None:
173+
load_example_env()
174+
vertex_model = build_vertex_model_config()
175+
run_vertex_flow("remote", vertex_model)
176+
run_vertex_flow("local", vertex_model)
177+
178+
179+
if __name__ == "__main__":
180+
main()

0 commit comments

Comments
 (0)