✈️ Travel Assistant

multi-tool capstone via Strands + Bedrock

Concept

The capstone demo: a multi-tool agent that checks the weather, suggests a packing list based on those conditions, prices the trip, and compares it to the budget. The agent chains four tools (plus the pre-built calculator) in the right order — nobody wrote that sequence. The agentic loop works it out from the system prompt and tool schemas.

Theory & Concepts

The Travel Assistant demo is the capstone showing autonomous multi-tool orchestration. When you ask for a trip plan, the request is passed to travel_agent.py, where the agent is equipped with four tools: get_weather_forecast, suggest_packing_list, estimate_trip_cost, and the calculator.

Crucially, there is no code in the demo that says "first call weather, then call packing list, then calculate cost." The agent's system prompt simply instructs it to check the weather, suggest packing based on that weather, and estimate the cost.

Amazon Nova Pro figures out the dependencies on the fly. It realizes it must call get_weather_forecast first. Once it receives the temperatures and conditions, it feeds those specific details into suggest_packing_list. Finally, it uses estimate_trip_cost (and the calculator if needed) to figure out the budget. This loop repeats autonomously until the agent has all the information to build the final response sent back to your browser.

Request flow

Browser trip query POST /travel
travel_agent.py plan() Agent
Bedrock: get_weather_forecast suggest_packing_list
estimate_trip_cost calculator (if needed)
final answer Browser

Code flow

flowchart TD A["Browser<br/>trip description"] -->|"POST /travel"| B["app.py<br/>travel_route"] B -->|"trip description"| C["travel_agent.py<br/>plan"] C -->|"query + tools"| D["Bedrock<br/>Nova Pro"] D -->|"tool call"| E["get_weather_forecast<br/>city, days"] E -->|"high/low/conditions"| D D -->|"tool call"| F["suggest_packing_list<br/>temp + conditions"] F -->|"items list"| D D -->|"tool call"| G["estimate_trip_cost<br/>city, days, travellers"] G -->|"cost breakdown"| D D -->|"final answer"| C C -->|"final answer"| B B -->|"JSON result"| A

Backend

travel_agent.py
"""Module 3 (capstone) demo — a multi-tool Travel Assistant.

The agent chains three custom tools plus a pre-built calculator: it checks the
weather first, feeds those numbers into a packing list, then prices the trip
and compares it to the budget. Nobody wrote that sequence — the agentic loop
works it out.
"""

from strands import Agent, tool
from strands.models.bedrock import BedrockModel
from strands_tools import calculator

from config import MODEL_ID, agent_text


@tool
def get_weather_forecast(city: str, days: int) -> dict:
    """Get the weather forecast for a city over a number of days.

    Args:
        city: Destination city name (e.g., "Goa", "Bangalore")
        days: Number of days in the trip
    """
    # Mock data — swap in a real weather API to take this further.
    forecasts = {
        "goa":       {"high_c": 32, "low_c": 26, "conditions": "humid, occasional showers"},
        "bangalore": {"high_c": 27, "low_c": 18, "conditions": "mild, light evening rain"},
        "jaipur":    {"high_c": 38, "low_c": 25, "conditions": "hot and dry"},
        "manali":    {"high_c": 14, "low_c": 3,  "conditions": "cold, chance of snow"},
    }
    data = forecasts.get(city.lower(), {"high_c": 28, "low_c": 20, "conditions": "moderate"})
    return {"city": city, "days": days, **data}


@tool
def suggest_packing_list(high_c: int, low_c: int, days: int, conditions: str) -> list:
    """Suggest what to pack based on temperatures, trip length and conditions.

    Args:
        high_c: Daytime high in Celsius
        low_c: Night-time low in Celsius
        days: Number of days in the trip
        conditions: Short description of expected weather
    """
    items = [f"{days + 1} sets of clothes", "toiletries", "phone charger"]
    if high_c >= 30:  # hot-weather threshold
        items += ["light cotton clothing", "sunscreen", "sunglasses", "reusable water bottle"]
    if low_c <= 15:  # cold-night threshold
        items += ["warm jacket", "thermal layer"]
    elif low_c <= 22:  # mild-night threshold — lighter layer than the cold case above
        items += ["light jacket for evenings"]
    # Keyword match on the forecast text rather than a structured field — fragile
    # if get_weather_forecast's wording changes, but keeps the tool signature simple.
    if "rain" in conditions.lower() or "shower" in conditions.lower():
        items += ["compact umbrella", "quick-dry footwear"]
    if "snow" in conditions.lower():
        items += ["gloves", "woollen cap", "waterproof boots"]
    return items


@tool
def estimate_trip_cost(city: str, days: int, travellers: int = 1) -> dict:
    """Estimate the cost of a trip in Indian rupees.

    Args:
        city: Destination city
        days: Number of days
        travellers: Number of people travelling (default: 1)
    """
    per_night = {"goa": 3500, "bangalore": 3000, "jaipur": 2500, "manali": 2800}
    stay = per_night.get(city.lower(), 3000) * days
    food = 1200 * days * travellers  # flat per-person, per-day food estimate (INR)
    local_travel = 800 * days  # flat per-day local transport estimate (INR), not per traveller
    total = stay + food + local_travel
    return {
        "city": city,
        "days": days,
        "travellers": travellers,
        "stay_inr": stay,
        "food_inr": food,
        "local_travel_inr": local_travel,
        "total_inr": total,
    }


def plan(question: str) -> str:
    """Plan a trip end-to-end, letting the agent chain its tools as needed."""
    agent = Agent(
        model=BedrockModel(model_id=MODEL_ID),
        tools=[get_weather_forecast, suggest_packing_list, estimate_trip_cost, calculator],
        system_prompt=(
            "You are a practical travel assistant. "
            "When asked about a trip: check the weather first, then suggest what to pack "
            "based on that weather, then estimate the cost. "
            "Always say whether the trip fits the user's budget, and keep advice concise."
        ),
        callback_handler=None,
    )
    return agent_text(agent(question))