From Vertex AI to Gemini Enterprise Agent Platform: Build a Travel Intent Parser with Express Mode (Part 1)
Prototypes are easy. Production boundaries are not.
Picture this: your executive team wants an AI travel concierge for our platform, Offvia. A customer types:
“Find me a four-day tech-conference trip to Tokyo from San Francisco for under $2,200, departing next Wednesday. I need a non-stop flight, a boutique hotel near Shibuya with high-speed Wi-Fi, and free cancellation in case my visa decision is delayed.”
You open your cloud console to build a prototype. But before you can send a single prompt, enterprise reality hits: you need a new GCP project, billing account approvals, IAM security clearance, and private VPC routing.
Estimated turnaround: three weeks.
By the time the security review clears, business momentum is dead.
Google Cloud recognized this enterprise bottleneck. Alongside evolving Vertex AI into Gemini Enterprise Agent Platform, Google introduced Express Mode—a zero-friction sandbox that compresses weeks of bureaucratic onboarding into a 30-second start.
In this first guide of our 6-part series, we will bypass the red tape, dissect Google Cloud’s agentic shift, and build a bulletproof, schema-constrained Python intent parser for Offvia.
📌 Official Terminology: Google Cloud officially designates this architecture as Gemini Enterprise Agent Platform (formerly Vertex AI). In this series, we explore its Express Mode sandbox alongside Agent Studio and the Agent Development Kit (ADK), backed by foundational Vertex AI capabilities and Gemini Enterprise.
✈️ The Mental Model: Fast-Track Gate vs Customs Clearance
To understand where Express Mode fits, picture an international airport:
When an airline crew lands for a 45-minute turnaround, they don’t stand in a two-hour general customs line. They flash their badge at the crew gate and walk straight onto the tarmac.
Conversely, permanent immigration requires rigorous visa checks and background audits.
- Express Mode is your Fast-Track Gate: An ephemeral sandbox for testing prompts, validating data schemas, and proving business value before lunch.
- Gemini Enterprise is your Customs Clearance: The governed runtime you graduate to before touching live credit cards, private customer PII, or internal VPC networks.
🏛️ The Paradigm Shift: Model Endpoints vs Cognitive Loops
For four years, Vertex AI operated on a model-centric paradigm: you sent tokens in, you received static tokens out. The model had no memory, no tools, and no operational awareness.
Modern Gemini Enterprise Agent Platform applications shift the entire center of gravity to autonomous cognitive loops:
(Gemini 2.5 Flash)"] ADK["🛠️ Orchestration, Sessions & Memory
(Google ADK / Agent Studio)"] MCP["🔌 Remote Enterprise Toolsets (MCP)
• Offvia BigQuery Lakehouse • Live Airline GDS APIs"] LLM -->|State Loop| ADK ADK -->|Tool Invocation| MCP MCP -->|Tool Execution Results| ADK ADK -->|Refined Working Memory| LLM end Engine --> Output["✅ Validated Data Contract, Flight Options, or Clarification Turn"] end
The platform unifies four critical primitives:
- Agent Studio: Visual playground to craft prompts, wire models, and export code.
- Agent Development Kit (ADK): Open-source framework in Python and TypeScript for multi-agent state loops.
- Agent Garden: Enterprise blueprints for common workflows (RAG, financial advisory, support).
- Gemini Enterprise Agent Platform MCP: Native Model Context Protocol (MCP) integration for connecting private lakehouses and external APIs securely.
🔍 Four Practitioner Questions Answered
Before writing code, let’s clear up the four most common enterprise misconceptions:
- Can Express Mode run real Python code?
Yes. It provisions a real GCP project. Your API key works immediately with the officialgoogle-genaiSDK from your local laptop, Docker container, or virtualenv. - Is an API key acceptable for production?
No. An API key authenticates the project for billing and rate limits, but it carries zero user identity. Production services must use Application Default Credentials (ADC) or Workload Identity with least-privilege IAM roles (roles/aiplatform.user). - Does structured JSON guarantee factual travel data?
No. A schema enforces syntax, not truth. Pydantic ensuresbudget_limitis a number, but cannot stop an LLM from inventing a non-existent airport or a fantasy $40 flight. Real-world validation belongs in downstream tools. - Can an Express Mode key access private BigQuery tables?
No. Google Cloud services enforce IAM boundaries. A model API key cannot be used as a bearer token to read private enterprise lakehouses.
🧪 Hands-On Lab: Build Offvia’s Travel-Intent Parser
Most AI agent tutorials jump straight into autonomous tool loops while ignoring the unglamorous foundation: data contracts. If your agent ingests unformatted human prose and emits unpredictable JSON, your downstream booking APIs will crash on call one.
Here is the architectural rule: Separate model extraction from domain reference resolution.
If a traveler says “Tokyo”, Tokyo has two major international hubs: Haneda (HND) and Narita (NRT). Do not ask the LLM to guess which one. Extract the city, flag it for resolution, and let your downstream flight tool expand it deterministically.
Step 1: Environment Setup
mkdir -p ~/offvia-agent-platform && cd ~/offvia-agent-platform
python3 -m venv .venv && source .venv/bin/activate
pip install --upgrade google-genai pydantic tzdata
Retrieve your API key from the Express Mode console and set your shell environment:
export GEMINI_API_KEY="your-express-mode-api-key"
export GEMINI_MODEL="gemini-2.5-flash"
Step 2: Write offvia_intent_parser.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Offvia Travel Concierge -- Schema-Constrained Intent Parser (Part 1)
Bootstrapped on Google Cloud Gemini Enterprise Agent Platform Express Mode.
"""
from __future__ import annotations
import os
import sys
import logging
from datetime import date, datetime
from time import perf_counter
from uuid import uuid4
from zoneinfo import ZoneInfo
from typing import List, Optional
from google import genai
from google.genai import types
from pydantic import BaseModel, Field, ValidationError, field_validator
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("offvia.intent_parser")
MODEL_ID = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
REFERENCE_TIMEZONE = os.getenv("OFFVIA_TIMEZONE", "Europe/Rome")
class HotelCriteria(BaseModel):
preferred_area: Optional[str] = Field(default=None, description="Neighborhood, e.g. Shibuya")
min_star_rating: Optional[float] = Field(default=None, ge=1.0, le=5.0)
required_amenities: List[str] = Field(default_factory=list)
free_cancellation_required: bool = Field(default=False)
class TripIntent(BaseModel):
# Separate raw traveler inputs from authoritative domain resolution
origin_city: str = Field(description="Origin city, e.g. San Francisco")
destination_city: str = Field(description="Destination city, e.g. Tokyo")
supplied_origin_iata: Optional[str] = Field(
default=None,
description="Populate ONLY if the traveler explicitly specified a 3-letter IATA code (e.g. SFO)"
)
airport_resolution_required: bool = Field(
default=True,
description="True when downstream tools must resolve city to airport codes (e.g. Tokyo -> HND/NRT)"
)
trip_duration_days: int = Field(ge=1, le=60)
# Preserve original wording for auditing alongside normalized ISO date
departure_date_expression: Optional[str] = Field(default=None, description="Raw expression, e.g. 'next Wednesday'")
departure_date: Optional[str] = Field(default=None, description="Resolved date in YYYY-MM-DD format")
budget_limit: float = Field(gt=0)
budget_currency: str = Field(description="3-letter currency code, e.g. USD")
non_stop_required: bool = Field(default=False)
hotel_preferences: HotelCriteria
special_constraints: List[str] = Field(default_factory=list)
unresolved_questions: List[str] = Field(default_factory=list)
@field_validator("budget_currency")
@classmethod
def validate_currency(cls, v: str) -> str:
code = v.strip().upper()
if len(code) != 3 or not code.isalpha():
raise ValueError("budget_currency must be a 3-letter code")
return code
@field_validator("departure_date")
@classmethod
def validate_date(cls, v: Optional[str]) -> Optional[str]:
if v:
date.fromisoformat(v)
return v
def build_client() -> genai.Client:
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise RuntimeError("GEMINI_API_KEY not set. Export your key first.")
return genai.Client(
vertexai=True,
api_key=api_key,
http_options=types.HttpOptions(
retry_options=types.HttpRetryOptions(attempts=4, initial_delay=1.0, max_delay=8.0)
),
)
def parse_traveler_request(client: genai.Client, request_text: str, ref_date: date, tz: str) -> TripIntent:
system_instruction = (
"You are the intent ingestion component for Offvia travel platform. "
"Extract only facts stated by the traveler. If the user states a city rather than an airport code, "
"leave supplied_origin_iata as null and set airport_resolution_required to true. "
"Capture raw date phrasing in departure_date_expression and normalized date in departure_date."
)
prompt = f"Reference Date: {ref_date.isoformat()}\nTimezone: {tz}\n\nQuery:\n{request_text}"
response = client.models.generate_content(
model=MODEL_ID,
contents=prompt,
config=types.GenerateContentConfig(
system_instruction=system_instruction,
response_mime_type="application/json",
response_schema=TripIntent,
),
)
return TripIntent.model_validate_json(response.text)
if __name__ == "__main__":
req_id = str(uuid4())[:8]
sample_request = (
"Find me a four-day tech-conference trip to Tokyo from San Francisco for no more than "
"$2,200, departing next Wednesday. I need a non-stop flight, a four-star hotel near "
"Shibuya with dedicated high-speed Wi-Fi, and free cancellation in case my visa decision is delayed."
)
ref_date = datetime.now(ZoneInfo(REFERENCE_TIMEZONE)).date()
logger.info("Processing request [%s] via model [%s]", req_id, MODEL_ID)
client = build_client()
start = perf_counter()
try:
intent = parse_traveler_request(client, sample_request, ref_date, REFERENCE_TIMEZONE)
elapsed_ms = (perf_counter() - start) * 1000
logger.info("Request [%s] parsed in %.0f ms", req_id, elapsed_ms)
print("\n=== Offvia Parsed Travel Contract ===")
print(intent.model_dump_json(indent=2))
except ValidationError as e:
logger.error("Schema validation failed: %s", e)
sys.exit(1)
finally:
client.close()
Step 3: Run and Inspect Output
python3 offvia_intent_parser.py
Output:
{
"origin_city": "San Francisco",
"destination_city": "Tokyo",
"supplied_origin_iata": null,
"airport_resolution_required": true,
"trip_duration_days": 4,
"departure_date_expression": "next Wednesday",
"departure_date": "2026-09-30",
"budget_limit": 2200.0,
"budget_currency": "USD",
"non_stop_required": true,
"hotel_preferences": {
"preferred_area": "Shibuya",
"min_star_rating": 4.0,
"required_amenities": [
"dedicated high-speed Wi-Fi"
],
"free_cancellation_required": true
},
"special_constraints": [
"Japanese visa decision might be delayed"
],
"unresolved_questions": []
}
Notice what happened:
- Zero Hallucination: Pydantic guarantees valid types. The output cannot be broken JSON.
- Date Traceability: Both
"next Wednesday"and"2026-09-30"are preserved for debugging. - Clean System Boundaries: The parser extracted the traveler’s intent; it made no false claims about flight availability or live pricing.
🛡️ Production Roadmap: What Comes Next?
Extracting a validated TripIntent object is step one. In a production architecture, the lifecycle expands into five governed stages:
Pydantic V2 Contract Validation"] S2["✈️ Stage 2: Domain Verification
IATA Codes, Currency & GDS Airline Rules"] S3["🤖 Stage 3: Multi-Agent Specialist Team
Supervisor Orchestration & State Machine"] S4["🔌 Stage 4: Remote MCP Toolsets
BigQuery Lakehouse & Real-Time APIs"] S5["👤 Stage 5: Human-in-the-Loop Governance
Explicit User Approval & Booking Execution"] S1 --> S2 --> S3 --> S4 --> S5
- Domain Lookup: Downstream services resolve “Tokyo” to
HNDandNRTvia authoritative database queries—never via LLM memory. - Quota Resiliency: Express Mode has lower rate limits. Production systems combine SDK exponential backoff with Redis token-bucket rate limiting.
- Secret Security: Graduate to Google Cloud Workload Identity Federation so static API keys never touch production source code.
- Human in the Loop: Agents propose bookings; they never charge corporate credit cards without an explicit human confirmation gate.
🚀 What’s Next in Part 2?
In Part 2: Visual Prototyping in Agent Studio — Designing the Offvia Concierge, we will:
- Recreate our intent instructions visually inside Agent Studio.
- Compare prompt variations and inspect the code generated by the studio canvas.
- Configure built-in tools and evaluate token consumption across complex multi-city itineraries.
- Bridge the visual studio draft directly into Google ADK (
google-adk) Python code.
📖 Official Documentation & Verified Standards
- Google Cloud Gemini Enterprise Overview
- Vertex AI Documentation
- Vertex AI Agent Builder & Generative AI Overview
- Google Cloud Application Default Credentials (ADC) Guide
- Structured Output & Schema Control with Gemini
- Google GenAI Python SDK Documentation
- Model Context Protocol (MCP) Specification
- Pydantic V2 Documentation





Community Discussion 0