I Had 47 Browser Tabs Open. So I Built an AI Travel Agent with Strands SDK + Amazon Bedrock ✈️
Okay, so real talk.
I had 47 browser tabs open.
47.....
Flights on a Flights App. Hotels on a Booking website. Weather on some sketchy website that was 40% ads. A spreadsheet I made at 11 PM that somehow had three separate columns all labeled "maybe hotel??" and one row that just said:
"check vibes"
All I wanted was to plan a week-long trip to Thailand.
And then — because I am a developer, and developers solve problems with code even when they absolutely should not — I closed all 47 tabs, opened Kiro's spec-driven mode, and said:
"Fine. I'll build the travel agent myself with Strands SDK."
Two hours later, I typed one sentence into my chat window and got back:
- Flights
- Hotels
- Weather warnings
- A complete 7-day itinerary
- Budget breakdowns
- Packing recommendations
- VISA options
And honestly?
I haven’t touched that spreadsheet since.
So let’s build the thing. 👇
Wait… What Even Is Strands SDK?
Before we dive in:
Strands SDK is AWS’s open-source Python framework for building AI agents.
Think of it like this:
- You write Python functions (tools)
- Add a
@tooldecorator - Connect it to a model on Amazon Bedrock
-
The model decides:
- which tools to call
- in what order
- with what inputs
That’s… basically it.
No YAML facades.
No 400-page orchestration configs.
No dependency graphs that look like a subway map.
Just Python functions and an LLM smart enough to use them.
Here’s the mental model that helped me:
Your Agent = A Smart Intern
Your Tools = What the Intern Can Do
Your Prompt = The Instructions
Bedrock = The Intern's Brain
Except this intern:
- never misses deadlines
10/10 would hire again.
What We’re Building
Our AI travel planner should:
- ✈️ Find flights
- 🏨 Find hotels
- 🌦️ Check weather
- 📋 Check Budgets and Build a full itinerary
One agent.
Four tools.
One prompt.
Done.
Architecture Overview
Stack Used
- Strands SDK → Agent framework
- Amazon Bedrock → Claude 3.5 Sonnet
- AWS Lambda → Tool execution
- Secrets Manager → API keys
- S3 → Save itineraries
- CloudWatch → Logging & observability
Yes, this is production-friendly.
We’ll get there.
Step 0 — Setup
Install dependencies:
pip install strands-agents strands-agents-tools boto3
Enable Claude 3.5 Sonnet inside Amazon Bedrock.
Takes ~2 minutes.
🚨 Please Don’t Hardcode API Keys
I know.
You’re “just testing.”
That API key will end up in a public GitHub repo eventually.
And then you will land up with a bill that burns your wallet.
Use AWS Secrets Manager instead.
import boto3
import json
def get_secret(secret_name: str) -> str:
"""Fetch API keys securely from AWS Secrets Manager."""
client = boto3.client(
"secretsmanager",
region_name="us-east-1"
)
response = client.get_secret_value(
SecretId=secret_name
)
return json.loads(
response["SecretString"]
)["api_key"]
One function call.
Sleep peacefully.
Tool #1 — Search Flights ✈️
One thing that blew my mind about Strands:
Your docstring is part of the prompt.
Not for developers. For the model.
The LLM reads your tool descriptions to decide:
- when to call them
- how to call them
- what arguments to pass
Good docstrings = smart agents.
Bad docstrings = chaos.
from strands import tool
@tool
def search_flights(
origin: str,
destination: str,
departure_date: str,
return_date: str,
num_passengers: int
) -> dict:
"""
Search for available flights between cities.
Returns:
- airline
- price
- duration
- stops
"""
api_key = get_secret(
"travel-agent/flight-api-key"
)
return {
"flights": [
{
"airline": "Thai Airways",
"price_per_person": 510,
"stops": 0,
"vibe": "Direct. Fancy. Worth it."
},
{
"airline": "Indigo",
"price_per_person": 440,
"stops": 1,
"vibe": "One stop. Still emotionally stable."
}
]
}
Yes, I added a "vibe" field.
No, the model doesn’t need it.
Yes, it made me happy and my decision making, faster....
Tool #2 — Hotels That Aren’t Secretly Hostels 🏨
You know what I hate?
The hotel listing that says:
"$89/night!"
And then somehow becomes:
- $89 base price
- taxes
- resort fee
- “service charge”
- “convenience fee”
- emotional damage
My tool doesn’t do that.
@tool
def search_hotels(
destination: str,
check_in: str,
check_out: str,
budget_per_night: float
) -> dict:
"""
Search hotels within budget.
"""
return {
"hotels": [
{
"name": "Veranda Resort",
"price_per_night": $155,
"rating": 4.8
},
{
"name": "Pacific Club Hotel",
"price_per_night": $120,
"rating": 4.5
}
],
"note": "No hidden fees. Unlike some websites."
}
Tool #3 — Weather Check 🌧️
Because packing goggles for Northern lights was a character-building experience I never want again.
@tool
def get_weather(
destination: str,
travel_month: str
) -> dict:
"""
Get weather info and packing advice.
"""
return {
"avg_temp_celsius": 26,
"conditions": "Mild with dry heat",
"packing_must_haves": [
"Light coloured t-shirts",
"beach flip flops",
"Umbrella",
"sunscreen",
]
}
Tiny tool. Massive usefulness.
Tool #4 — Build the Itinerary 📋
This is where everything comes together.
Flights ✅
Hotels ✅
Weather ✅
Now the agent can finally build a realistic itinerary.
And yes — we save it to S3.
Because my Notes app cannot be trusted.
from datetime import datetime
@tool
def build_itinerary(
destination: str,
duration_days: int,
interests: list,
budget_remaining: float
) -> dict:
"""
Build a day-by-day itinerary.
"""
itinerary = {
"destination": destination,
"days": duration_days,
"interests": interests,
"daily_plan": [
{
"day": 1,
"theme": "Land and Eat Immediately"
},
{
"day": 2,
"theme": "Markets and Sunset Beach Walks"
}
]
}
s3 = boto3.client("s3")
s3.put_object(
Bucket="travel-itineraries",
Key="thai-trip.json",
Body=json.dumps(itinerary)
)
return itinerary
Assemble the Agent
Now the fun part.
from strands import Agent
from strands.models import BedrockModel
model = BedrockModel(
model_id="anthropic.claude-3-5-sonnet",
region_name="us-east-1",
temperature=0.3
)
SYSTEM_PROMPT = """
You are a smart AI travel planner.
Always:
1. Search flights first
2. Search hotels second
3. Check weather
4. Build itinerary for given budget last
"""
travel_agent = Agent(
model=model,
system_prompt=SYSTEM_PROMPT,
tools=[
search_flights,
search_hotels,
get_weather,
build_itinerary
]
)
response = travel_agent(
"""
Plan a 5-day Thailand trip for 2 people under $3000.
"""
)
print(response)
Terminal Output
[Tool Call] search_flights
✓ Thai Airways selected
[Tool Call] search_hotels
✓ Veranda Resort selected
[Tool Call] get_weather
✓ Mild weather detected
[Tool Call] build_itinerary
✓ Saved to S3
━━━━━━━━━━━━━━━━━━
YOUR THAILAND TRIP
━━━━━━━━━━━━━━━━━━
Flights: $1020
Hotel: $775
Activities: $1200
TOTAL: $2995 ✅
That ✅ Under budget hit different.
Production Lessons (Learned the Hard Way)
Here’s the honest truth:
Your local demo is not your production system.
Things that will hurt you eventually:
| Problem | Fix |
|---|---|
| Hardcoded keys | Secrets Manager |
| Infinite tool loops | Max tool-call limits |
| No observability | CloudWatch logging |
| Surprise Bedrock bill |
max_tokens + alarms |
| Zero error handling |
try/except everywhere |
| Unsafe prompts | Bedrock Guardrails |
Add Logging with CloudWatch
import logging
from strands.handlers import CallbackHandler
logger = logging.getLogger("travel-agent")
class LoggingHandler(CallbackHandler):
def on_tool_call(
self,
tool_name,
tool_input
):
logger.info(
f"Calling {tool_name}"
)
def on_error(self, error):
logger.error(str(error))
This is the difference between:
- “cool demo”
- and “actual software”
My Biggest Takeaways
1. Your Docstrings Matter More Than You Think
The model uses them as instructions.
Treat them seriously.
2. Tool Order Matters
Without ordering rules, my agent built itineraries before knowing hotel costs.
Beautiful plans. Completely wrong budget.
3. Lower Temperature = Better Planning
High temperature is great for creativity.
Not for budgeting.
4. Persist Everything
S3. DynamoDB. Databases.
Conversation memory disappears eventually.
Your data shouldn’t.
5. The Demo → Production Gap Is Real
Prototypes and demos are magical.
Production systems are where reality shows up with a baseball bat.
Build responsibly.
FAQ
Do I Need a Strands Certification?
Nope.
Just:
- Python
- Bedrock access
- Functions
That’s enough to start.
Can I Use Other Models?
Absolutely.
Swap Claude for:
- Llama
- Mistral
- Titan
- Anything available on Bedrock
My Agent Calls Tools in Weird Orders
Fix your system prompt.
Be explicit.
LLMs love explicit instructions.
What’s Next?
You can extend this into:
- 🧠 Memory with DynamoDB
- 💬 Slack bots
- 🤖 Multi-agent orchestration
- 🏭 Bedrock AgentCore deployments
That’s where things get really interesting.
I started this journey with:
- 47 browser tabs
- one broken spreadsheet
- and travel-planning rage
I ended it with:
- a functioning AI travel agent
- a Thailand itinerary
- and a permanently closed spreadsheet
Honestly?
Worth it.
If you enjoyed this post:
- ❤️ Drop a reaction
- 🔁 Share it with another developer
- 👀 Follow for more AI agent tutorials
And if you build something cool with Strands SDK — I genuinely want to see it. Hit the comments box or reach out over LinkedIn. I work for AWS and love building and exchanging views on AI!
Happy building. ✈️

Top comments (0)