Build Aura: GenAI Travel Concierge with ADK

We live in a transformative era. As of October 2025, the conversation around AI has decisively shifted from “what can it do?” to “what can we build with it?” Today, we’re moving beyond simple chatbots to create sophisticated, autonomous AI agents that can reason, plan, and execute complex tasks.

Please request access with valid justification for the following:
To see it in action, click on Aura ADK Demo Video (4K).
To check code, click on Aura Agent Code.

This is the story of Aura, a full-stack AI Travel Concierge for Indian cities. But more than that, it’s a blueprint. It’s a deep dive into the architecture, customer journeys, and deployment path for a modern AI agent, built with Google’s Agent Development Kit (ADK), Gemini 2.0-flash, and BigQuery.

adk_demo_1510_1024p-ezgif.com-video-to-gif-converter

Whether you’re a developer, a product manager, or just an AI enthusiast, this guide will walk you through the entire lifecycle—from initial concept to a globally scalable application.


:hammer_and_wrench: Prerequisites: Setting up your agent workshop

Before we can build, we need to set up our workshop. This guide assumes you’re on a macOS or Linux-based system.

1. Google Cloud setup:

  • Create a Google Cloud Project: If you don’t have one already, create a new project in the Google Cloud Console.

  • Enable APIs: In your new project, enable the BigQuery API and the Vertex AI API. This allows our agent to use these services.

  • Install the gcloud CLI: Follow the instructions to install the Google Cloud SDK.

  • Authenticate: Log in with your Google account by running:

    gcloud auth application-default login
    

2. Local development environment:

  • Python: Ensure you have Python 3.10 or higher installed.

  • Create a Virtual Environment: This is crucial for managing dependencies.

    python3 -m venv venv
    source venv/bin/activate
    
  • Install dependencies: We’ll need a few Python libraries for Aura.

    pip install google-cloud-bigquery pandas Faker requests pandas-gbq
    

3. Configure your Gemini API key:

  • Get Your Key: Obtain your Gemini API key from the Google AI for Developers website.

  • Create a .env file: This file will securely store your API key. Inside the /tourism_assistant directory (which you will create inside the adk folder), create a new file named .env.

  • Verify the file: Sometimes this file can be hidden. You can check that it exists using the command ls -la tourism_assistant. You should see .env in the list.

  • Add your key: Open the .env file and add the following line, replacing "your-key-here" with your actual key:

    GOOGLE_API_KEY="your-key-here"
    

With your workshop set up, you’re ready to build Aura!


:woman_walking: The customer journey: Aura in the wild

An agent’s success is defined by the value it provides to its users. Before we look at a single line of code, let’s imagine how different people might interact with Aura.

Journey 1: Anjali, the meticulous planner

Anjali is planning her first trip to Rajasthan. She has a clear goal and follows a logical path.

  1. Itinerary first: She asks, “Plan a 3-day trip to Jaipur.”
  2. Aura’s response: Aura executes its “Master Workflow,” calling multiple tools internally to generate a detailed, day-by-day itinerary complete with fun facts and travel times between sights.
  3. Next logical step - Hotels: Impressed, Anjali follows Aura’s proactive suggestion and asks, “Show me some mid-range hotels.” Aura queries its BigQuery database and returns a list of top-rated, budget-appropriate hotels.
  4. Final step - Travel: Anjali decides to travel to Delhi next. She asks, “What are my travel options to get to Delhi?” Aura dynamically generates a list of plausible flights, trains, and buses.

Outcome: Anjali has a complete travel plan, from itinerary to accommodation to transit, all from a single, seamless conversation.


Journey 2: Rohan, the spontaneous explorer

Rohan is already in Bangalore and is looking for something to do tonight. His journey is non-linear.

  1. A quick tip: He asks, “What’s a good place for dinner in Bangalore?”
  2. Aura’s response: Aura doesn’t need to build a full itinerary. It recognizes the specific request, calls its find_top_rated_restaurant tool, and immediately suggests “Mavalli Tiffin Rooms (MTR)” for authentic South Indian cuisine.
  3. Sparking an idea: Intrigued, Rohan asks, “Okay, can you plan a full day around that for tomorrow?” This now triggers Aura’s main workflow, building a 1-day itinerary for Bangalore.

Outcome: Aura adapts to the user’s context, providing both quick, single-purpose answers and full, detailed plans as needed.



:gear: The Core architecture: ADK, MCP, and the Agent Engine

To build a sophisticated agent, it’s crucial to understand the roles of three distinct concepts:

  1. The Agent Development Kit (ADK): This is your developer’s toolkit. It’s the framework you use on your local machine to write, test, and debug your agent. It provides the Agent and FunctionTool classes, the local web server (adk web), and the command-line interface (adk run). Think of it as the IDE and testing suite for your agent.

  2. The Model Context Protocol (MCP) Server: This is an architectural pattern. An MCP server’s job is to expose tools (especially complex ones like database queries) to an agent over a network. While our Aura agent defines tools directly in Python for simplicity, more complex systems might use a dedicated MCP server to serve tools. The ADK’s web server implicitly acts as this server in our setup.

  3. The Agent Engine: This is your production deployment environment. While the ADK is your local engine, a true Agent Engine is a scalable, managed platform like Google Cloud Run or Vertex AI Agent Engine. Its job is to run your agent, handle incoming user traffic, and scale automatically.

In short: you build with the ADK, you can serve tools with an MCP Server, and you deploy to an Agent Engine.


:classical_building: The anatomy of Aura: Code & Prompt

Now let’s look at the specific code and prompt that the ADK uses to build its context.

Code: Aura’s library of superpowers

This is where the magic happens. We’ve equipped Aura with a suite of specialized Python functions. Let’s dive into the full code for each one.

  • The Blueprint Builder: generate_travel_itinerary
    This foundational tool queries BigQuery for attractions and creates a machine-readable “skeleton” plan for the agent to enrich.

    def generate_travel_itinerary(city: str, num_days: int) -> str:
        """Generates a structured, day-by-day skeleton of top-rated attractions for a trip."""
        query = f"""
        SELECT attraction.name, attraction.type, attraction.latitude, attraction.longitude
        FROM `{BIGQUERY_TABLE_ID}`, UNNEST(attractions) AS attraction
        WHERE LOWER(destination_name) = LOWER(@city) AND attraction.type NOT IN ('Restaurant', 'Cafe', 'Market')
        ORDER BY attraction.rating DESC
        LIMIT {num_days * 2}
        """
        params = [bigquery.ScalarQueryParameter("city", "STRING", city)]
        try:
            df = db_client.query(query, job_config=bigquery.QueryJobConfig(query_parameters=params)).to_dataframe()
            if df.empty: return f"Error: Could not find attractions for {city}."
    
            itinerary_str = f"SKELETON_ITINERARY_FOR:{city}:{num_days}_DAYS\n"
            for i in range(num_days):
                day = i + 1
                itinerary_str += f"DAY:{day}\n"
                if (i*2) < len(df): itinerary_str += f"MORNING:{df.iloc[i*2]['name']}|{df.iloc[i*2]['type']}|{df.iloc[i*2]['latitude']}|{df.iloc[i*2]['longitude']}\n"
                if (i*2+1) < len(df): itinerary_str += f"AFTERNOON:{df.iloc[i*2+1]['name']}|{df.iloc[i*2+1]['type']}|{df.iloc[i*2+1]['latitude']}|{df.iloc[i*2+1]['longitude']}\n"
            return itinerary_str
        except Exception as e:
            return f"Error: Failed to generate itinerary skeleton: {e}"
    
  • The Hotelier: find_hotels
    This tool provides data-grounded hotel suggestions from our BigQuery database based on budget.

    def find_hotels(city: str, budget_level: str = 'mid-range') -> str:
        """Finds top-rated hotels in a city from the database based on a budget level."""
        price_map = {'budget': 150, 'mid-range': 400, 'luxury': 1000}
        max_price = price_map.get(budget_level, 400)
    
        query = f"""
        SELECT h.name, h.star_rating, h.rating, h.price_per_night_usd
        FROM `{BIGQUERY_TABLE_ID}`, UNNEST(accommodations) AS h
        WHERE LOWER(destination_name) = LOWER(@city) AND h.price_per_night_usd <= @max_price
        ORDER BY h.rating DESC
        LIMIT 3
        """
        params = [
            bigquery.ScalarQueryParameter("city", "STRING", city),
            bigquery.ScalarQueryParameter("max_price", "INT64", max_price)
        ]
        try:
            df = db_client.query(query, job_config=bigquery.QueryJobConfig(query_parameters=params)).to_dataframe()
            if df.empty: return "HOTELS_NOT_FOUND"
            return df.to_markdown(index=False)
        except Exception as e:
            return f"HOTELS_ERROR: {e}"
    
  • The Logistics Officer: suggest_transportation
    This tool dynamically generates plausible, fake travel options, showcasing how an agent can be creative and responsible.

    def suggest_transportation(origin_city: str, destination_city: str) -> str:
        """Generates realistic but fake travel suggestions for flights, trains, and buses."""
        if origin_city.lower() == destination_city.lower():
            return "TRANSPORT_ERROR: Origin and destination cities cannot be the same."
    
        response = f"### Travel Options from {origin_city} to {destination_city}\n\n"
    
        airlines = {"Delhi": "IndiGo", "Jaipur": "Air India", "Bangalore": "Vistara", "Goa": "SpiceJet"}
        flight_num = random.randint(100, 999)
        price_flight = random.randint(2500, 7000)
        response += f"✈️ **Flights**\n- **{airlines.get(origin_city, 'IndiGo')} 6E-{flight_num}**: Approx. ₹{price_flight}, Travel Time: ~2 hours\n"
    
        trains = {"Delhi-Jaipur": "Ajmer Shatabdi (12015)", "Delhi-Bangalore": "Karnataka Express (12628)"}
        train_key = f"{origin_city}-{destination_city}"
        train_name = trains.get(train_key, f"Duronto Express ({random.randint(12200, 12299)})")
        price_train = random.randint(800, 3000)
        response += f"\n🚂 **Trains**\n- **{train_name}**: Approx. ₹{price_train} (AC Chair Car), Travel Time: 5-8 hours\n"
    
        response += "\n_Disclaimer: These are sample suggestions. Please check with official providers for real-time availability and prices._"
        return response
    
  • The Storyteller: get_fun_fact
    This tool enriches the itinerary by calling the free DuckDuckGo API for a fun fact.

    def get_fun_fact(topic: str) -> str:
        """Finds an interesting fun fact about a topic using the DuckDuckGo API."""
        url = f"[https://api.duckduckgo.com/?q=](https://api.duckduckgo.com/?q=){topic}&format=json"
        try:
            response = requests.get(url, headers={'User-Agent': 'AuraTravelConcierge/1.0'})
            response.raise_for_status()
            data = response.json()
            abstract = data.get("AbstractText")
            if abstract: return f"FUN_FACT_FOR_{topic.replace(' ', '_')}: {abstract.split('.')[0]}."
            return f"FUN_FACT_NOT_FOUND"
        except Exception:
            return f"FUN_FACT_ERROR"
    
  • The Local Guide: translate_text
    This tool makes the agent proactively helpful by translating phrases using the free MyMemory API.

    def translate_text(text_to_translate: str, target_language: str) -> str:
        """Translates English text to a specified target language using a free, key-less API."""
        lang_map = {"hindi": "hi", "kannada": "kn"}
        target_code = lang_map.get(target_language.lower())
        if not target_code:
            return "TRANSLATION_ERROR: Unsupported language. Please choose from Hindi or Kannada."
    
        url = f"[https://api.mymemory.translated.net/get?q=](https://api.mymemory.translated.net/get?q=){requests.utils.quote(text_to_translate)}&langpair=en|{target_code}"
        try:
            response = requests.get(url, headers={'User-Agent': 'AuraTravelConcierge/1.0'})
            response.raise_for_status()
            data = response.json()
            if data['responseStatus'] == 200:
                return f"TRANSLATION_SUCCESS:{text_to_translate} -> {data['responseData']['translatedText']}"
            else:
                return f"TRANSLATION_ERROR: {data['responseDetails']}"
        except Exception as e:
            return f"TRANSLATION_ERROR: Failed to connect to translation service: {e}"
    

Prompt: The agent’s soul and “Master Workflow”

The instruction prompt is where we define Aura’s personality and its complex, multi-step reasoning process. The full code block below shows how we combine all components using the ADK’s Agent class.

# The complete agent definition in agent.py
root_agent = Agent(
    name="Aura_AI_Travel_Concierge_India",
    model="gemini-2.0-flash",
    description="A sophisticated AI travel concierge for India that crafts story-rich itineraries and provides helpful language translations.",
    instruction="""You are Aura, an elite AI Travel Concierge specializing in Indian travel. Your mission is to deliver a complete, beautifully crafted travel plan by following a strict, multi-step internal monologue.

**Aura's Master Workflow: The path to a perfect trip**

1.  **Step 1: Build the Skeleton (Internal Tool Call):** Call `generate_travel_itinerary`.
2.  **Step 2: Enrich the Blueprint (Multiple Internal Tool Calls):** Call `calculate_travel_time`, `find_top_rated_restaurant`, and `get_fun_fact`.
3.  **Step 3: Synthesize and Present the Masterpiece:** Combine all information into a single response.
4.  **Step 4: Proactive Assistance:** Proactively offer to find hotels, then suggest transportation, and finally offer to translate a phrase.

**Your Core Directive:**
Follow the **"Build -> Enrich -> Synthesize -> Assist (Hotels -> Transport -> Translate)"** workflow religiously. Guide the user seamlessly through the entire planning process.
""",
    tools=[
        FunctionTool(func=generate_travel_itinerary),
        FunctionTool(func=get_fun_fact),
        FunctionTool(func=calculate_travel_time),
        FunctionTool(func=find_top_rated_restaurant),
        FunctionTool(func=find_hotels),
        FunctionTool(func=suggest_transportation),
        FunctionTool(func=translate_text),
    ],
)



:rocket: How to run the code

With your prerequisites configured and your agent.py file created, running Aura is incredibly simple thanks to the ADK.

  1. Ensure you are in the correct directory: Your terminal should be in the root folder, i.e., directory having agent.py.

  2. Activate your virtual environment:

    source venv/bin/activate
    
  3. Run Aura in one of two modes:

    • Interactive Command-Line Mode:
      This is perfect for quick tests and development, as you’ll see the agent’s thoughts and tool calls directly in the terminal.

      ./venv/bin/adk run tourism_assistant
      
    • Sleek Web Interface:
      To showcase your agent with a shareable UI, use the web command.

      ./venv/bin/adk web .
      

    The ADK will start a local server (usually on `http://127.0.0.1:8000`) and provide a URL to a beautiful chat interface right in your browser.

:crystal_ball: The road ahead: From prototype to production

We’ve built a powerful prototype. Now, how do we take it to the next level and deploy it for the world to use?

  • Evolving the Agent Engine:

  • Model: Upgrade to Gemini 2.5 Pro. With its massive 1 million token context window and native multi-modality, Aura could understand user-uploaded images (“Find me a hotel that looks like this picture of a haveli!”) or even analyze short videos of a destination.

  • Code:

    • Give Aura a Memory: Integrate Firestore to store user preferences across conversations.

    • Connect to the Real World: Use Vertex AI Search to ground Aura in real-time data from trusted travel websites or APIs, moving beyond our static database.

  • Prompt: As we add new tools, we’ll refine the “Master Workflow” in the prompt, teaching Aura how and when to use its new capabilities.

  • Deploying with Cloud Run & Docker: The ADK’s local web server is great for development, but for a production application, we need a scalable, robust solution. Enter Docker and Cloud Run

    • Containerize with Docker: We create a Dockerfile. This is a recipe that packages our entire agent—the Python code, the ADK, and all its dependencies—into a lightweight, portable container.

    • Deploy with Cloud Run: Cloud Run is a fully managed, serverless platform. We simply give it our Docker container, and it handles everything else, from providing a public HTTPS endpoint to autoscaling.


:sparkles: Conclusion: You’ve built an agent!

Congratulations! You’ve just walked through the complete lifecycle of a modern AI agent. You’ve seen how to combine the reasoning power of Gemini, the real-world capabilities of Python code, and the guiding intelligence of a detailed prompt.

Most importantly, you’ve seen how the Agent Development Kit acts as the crucial development framework and local Model Context Protocol server that makes this all possible, providing the structure to build, test, and ultimately deploy sophisticated AI experiences to a production Agent Engine like Cloud Run. The journey from a simple idea to a scalable, intelligent agent is more accessible than ever. The tools are here, the path is clear, and the possibilities are endless.

Let’s keep the conversation going! Share your thoughts, questions, and ideas in the comments.

Note: Should you have any concerns or queries about this post or my implementation, please feel free to connect with me on LinkedIn! Thanks!


:books: References and further reading

Ready to start your own agent-building journey? Here are the official resources and foundational research to get you started.

Official documentation

Foundational research papers

Demos

We can’t wait to see what you build. Share your creations and ask questions in the Google Cloud Community. Happy coding!

3 Likes

A must-read article for every agent builder!

1 Like

Hi, very interesting. However, what does “valid justification” mean in this context? I understand this is posted as a public post to a public forum.

2 Likes

Thank you for pointing that out. I’ve submitted the code to be merged into the Github repository for easier access. In the interim, I’m providing it via Google Drive and code snippets in the blog; please note that a message is required when requesting access but it will be made available to everyone soon after reviews.

1 Like