Skip to content

Commit b45a34e

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: faster sandbox creation with templates and snapshots and improve dataplane routing and security.
Multitenancy Sandbox support PiperOrigin-RevId: 906714249
1 parent 68f053e commit b45a34e

2 files changed

Lines changed: 83 additions & 20 deletions

File tree

vertexai/_genai/sandboxes.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ def _CreateAgentEngineSandboxConfig_to_vertex(
5656
if getv(from_object, ["ttl"]) is not None:
5757
setv(parent_object, ["ttl"], getv(from_object, ["ttl"]))
5858

59+
if getv(from_object, ["sandbox_environment_template"]) is not None:
60+
setv(
61+
parent_object,
62+
["sandboxEnvironmentTemplate"],
63+
getv(from_object, ["sandbox_environment_template"]),
64+
)
65+
66+
if getv(from_object, ["sandbox_environment_snapshot"]) is not None:
67+
setv(
68+
parent_object,
69+
["sandboxEnvironmentSnapshot"],
70+
getv(from_object, ["sandbox_environment_snapshot"]),
71+
)
72+
73+
if getv(from_object, ["owner"]) is not None:
74+
setv(parent_object, ["owner"], getv(from_object, ["owner"]))
75+
5976
return to_object
6077

6178

@@ -837,7 +854,7 @@ def delete(
837854
def generate_access_token(
838855
self,
839856
service_account_email: str,
840-
sandbox_id: str,
857+
sandbox_hostname: str,
841858
port: str = "8080",
842859
timeout: int = 3600,
843860
) -> str:
@@ -846,8 +863,8 @@ def generate_access_token(
846863
Args:
847864
service_account_email (str):
848865
Required. The email of the service account to use for signing.
849-
sandbox_id (str):
850-
Required. The resource name of the sandbox to generate a token for.
866+
sandbox_hostname (str):
867+
Required. The hostname of the sandbox to generate a token for.
851868
port (str):
852869
Optional. The port to use for the token. Defaults to "8080".
853870
timeout (int):
@@ -858,13 +875,14 @@ def generate_access_token(
858875
"""
859876
client = iam_credentials_v1.IAMCredentialsClient()
860877
name = f"projects/-/serviceAccounts/{service_account_email}"
861-
custom_claims = {"port": port, "sandbox_id": sandbox_id}
878+
custom_claims = {"hostname": sandbox_hostname, "port": port}
862879
payload = {
863880
"iat": int(time.time()),
864881
"exp": int(time.time()) + timeout,
865882
"iss": service_account_email,
883+
"sub": service_account_email,
866884
"nonce": secrets.randbelow(1000000000) + 1,
867-
"aud": "vmaas-proxy-api", # default audience for sandbox proxy
885+
"aud": "https://aiplatform.googleapis.com/", # default audience for sandbox proxy
868886
**custom_claims,
869887
}
870888
request = iam_credentials_v1.SignJwtRequest(
@@ -880,6 +898,7 @@ def send_command(
880898
http_method: str,
881899
access_token: str,
882900
sandbox_environment: types.SandboxEnvironment,
901+
port: str = "8080",
883902
path: Optional[str] = None,
884903
query_params: Optional[dict[str, object]] = None,
885904
headers: Optional[dict[str, str]] = None,
@@ -894,6 +913,8 @@ def send_command(
894913
Required. The access token to use for authorization.
895914
sandbox_environment (types.SandboxEnvironment):
896915
Required. The sandbox environment to send the command to.
916+
port (str):
917+
Optional. The port to use for the token. Defaults to "8080". This should be one of the ports specified during template creation.
897918
path (str):
898919
Optional. The path to send the command to.
899920
query_params (dict[str, object]):
@@ -918,10 +939,14 @@ def send_command(
918939
else:
919940
raise ValueError("Load balancer hostname or ip is not available.")
920941

942+
routing_token = connection_info.routing_token
943+
921944
path = path or ""
922945
if query_params:
923946
path = f"{path}?{urlencode(query_params)}"
924947
headers["Authorization"] = f"Bearer {access_token}"
948+
headers["X-Sandbox-Routing-Token"] = routing_token
949+
headers["X-Sandbox-Port"] = port
925950
endpoint = endpoint + path if path.startswith("/") else endpoint + "/" + path
926951
http_options = genai_types.HttpOptions(headers=headers, base_url=endpoint)
927952
http_client = genai.Client(vertexai=True, http_options=http_options)
@@ -937,6 +962,7 @@ def generate_browser_ws_headers(
937962
self,
938963
sandbox_environment: types.SandboxEnvironment,
939964
service_account_email: str,
965+
port: str = "8080",
940966
timeout: int = 3600,
941967
) -> tuple[str, dict[str, str]]:
942968
"""Generates the websocket upgrade headers for the browser.
@@ -946,47 +972,59 @@ def generate_browser_ws_headers(
946972
Required. The sandbox environment to generate websocket headers for.
947973
service_account_email (str):
948974
Required. The email of the service account to use for signing.
975+
port (str):
976+
Optional. The port to use for the token. Defaults to "8080". This
977+
should be one of the ports specified during template creation.
949978
timeout (int):
950979
Optional. The timeout in seconds for the token. Defaults to 3600.
951980
952981
Returns:
953982
tuple[str, dict[str, str]]: A tuple containing the websocket URL and
954983
the headers for websocket upgrade.
955984
"""
956-
sandbox_id = sandbox_environment.name
985+
if not sandbox_environment.connection_info:
986+
raise ValueError("Connection info is not available.")
987+
988+
ws_url = "wss://test-us-central1.autopush-sandbox.vertexai.goog"
989+
connection_info = sandbox_environment.connection_info
990+
if connection_info.load_balancer_hostname:
991+
ws_base_url = "wss://" + connection_info.load_balancer_hostname
992+
elif connection_info.load_balancer_ip:
993+
ws_base_url = "ws://" + connection_info.load_balancer_ip
994+
else:
995+
raise ValueError("Load balancer hostname or ip is not available.")
996+
957997
# port 8080 is the default port for http endpoint.
958998
http_access_token = self.generate_access_token(
959-
service_account_email, sandbox_id, "8080", timeout
999+
service_account_email, connection_info.load_balancer_hostname, port, timeout
9601000
)
9611001
response = self.send_command(
9621002
http_method="GET",
9631003
access_token=http_access_token,
9641004
sandbox_environment=sandbox_environment,
1005+
port=port,
9651006
path="/cdp_ws_endpoint",
9661007
)
9671008
if not response:
9681009
raise ValueError("Failed to get the websocket endpoint.")
9691010
body_dict = json.loads(response.body)
9701011
ws_path = body_dict["endpoint"]
971-
972-
ws_url = "wss://test-us-central1.autopush-sandbox.vertexai.goog"
973-
if sandbox_environment and sandbox_environment.connection_info:
974-
connection_info = sandbox_environment.connection_info
975-
if connection_info.load_balancer_hostname:
976-
ws_url = "wss://" + connection_info.load_balancer_hostname
977-
elif connection_info.load_balancer_ip:
978-
ws_url = "ws://" + connection_info.load_balancer_ip
979-
else:
980-
raise ValueError("Load balancer hostname or ip is not available.")
981-
ws_url = ws_url + "/" + ws_path
1012+
ws_url = ws_base_url + "/" + ws_path
9821013

9831014
# port 9222 is the default port for the browser websocket endpoint.
9841015
ws_access_token = self.generate_access_token(
985-
service_account_email, sandbox_id, "9222", timeout
1016+
service_account_email,
1017+
connection_info.load_balancer_hostname,
1018+
"9222",
1019+
timeout,
9861020
)
9871021

1022+
routing_token = connection_info.routing_token
1023+
9881024
headers = {}
989-
headers["Sec-WebSocket-Protocol"] = f"binary, {ws_access_token}"
1025+
headers["Sec-WebSocket-Protocol"] = (
1026+
f"v1.stream, {ws_access_token}, {routing_token}, {port}"
1027+
)
9901028
return ws_url, headers
9911029

9921030

vertexai/_genai/types/common.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11733,6 +11733,20 @@ class CreateAgentEngineSandboxConfig(_common.BaseModel):
1173311733
default=None,
1173411734
description="""The TTL for this resource. The expiration time is computed: now + TTL.""",
1173511735
)
11736+
sandbox_environment_template: Optional[str] = Field(
11737+
default=None,
11738+
description="""The name of the sandbox environment template to create the sandbox from. The sandbox environment template should be in the format:
11739+
projects/{project}/locations/{location}/agentEngines/{agent_engine}/sandboxEnvironmentTemplates/{sandbox_environment_template}""",
11740+
)
11741+
sandbox_environment_snapshot: Optional[str] = Field(
11742+
default=None,
11743+
description="""The name of the sandbox environment snapshot to restore the sandbox from. The sandbox environment snapshot should be in the format:
11744+
projects/{project}/locations/{location}/agentEngines/{agent_engine}/sandboxEnvironmentSnapshots/{sandbox_environment_snapshot}""",
11745+
)
11746+
owner: Optional[str] = Field(
11747+
default=None,
11748+
description="""Owner information for this sandbox environment. A sandbox can only be restored from a snapshot belonging to the same owner.""",
11749+
)
1173611750

1173711751

1173811752
class CreateAgentEngineSandboxConfigDict(TypedDict, total=False):
@@ -11753,6 +11767,17 @@ class CreateAgentEngineSandboxConfigDict(TypedDict, total=False):
1175311767
ttl: Optional[str]
1175411768
"""The TTL for this resource. The expiration time is computed: now + TTL."""
1175511769

11770+
sandbox_environment_template: Optional[str]
11771+
"""The name of the sandbox environment template to create the sandbox from. The sandbox environment template should be in the format:
11772+
projects/{project}/locations/{location}/agentEngines/{agent_engine}/sandboxEnvironmentTemplates/{sandbox_environment_template}"""
11773+
11774+
sandbox_environment_snapshot: Optional[str]
11775+
"""The name of the sandbox environment snapshot to restore the sandbox from. The sandbox environment snapshot should be in the format:
11776+
projects/{project}/locations/{location}/agentEngines/{agent_engine}/sandboxEnvironmentSnapshots/{sandbox_environment_snapshot}"""
11777+
11778+
owner: Optional[str]
11779+
"""Owner information for this sandbox environment. A sandbox can only be restored from a snapshot belonging to the same owner."""
11780+
1175611781

1175711782
CreateAgentEngineSandboxConfigOrDict = Union[
1175811783
CreateAgentEngineSandboxConfig, CreateAgentEngineSandboxConfigDict

0 commit comments

Comments
 (0)