AWS Builder Center
Building a Multi-Agent AI Travel Request Assistant on AWS with Strands Agents SDK and Amazon Bedrock

Building a Multi-Agent AI Travel Request Assistant on AWS with Strands Agents SDK and Amazon Bedrock

A five-agent corporate travel pipeline on AWS that automates policy compliance, budget validation, GDS booking, tiered manager approvals with auto-escalation, and employee notifications — end to end.

Building a Multi-Agent AI Travel Request Assistant on AWS with Strands Agents SDK and Amazon Bedrock

GitHub: https://github.com/its-me-sweta/Travel_Request_AI_Assistant.git

Introduction

Corporate travel management is a high-friction, multi-step process that touches policy compliance, budget controls, booking systems, multi-level approvals, and employee communication — all before a single ticket is issued.
In this post I'll walk you through how I built a production-ready, five-agent AI pipeline that automates the entire corporate travel request lifecycle using:
  • Strands Agents SDK with @tool decorators
  • Amazon Bedrock Nova Pro (us.amazon.nova-pro-v1:0) as the LLM backbone
  • Amazon RDS Aurora for policy, budget, and booking data
  • AWS Lambda for GDS (Global Distribution System) integration
  • Amazon DynamoDB for pipeline state, approvals, and audit trails
  • Amazon SES + SNS for employee and manager notifications
  • AgentCore Runtime for serverless deployment
  • FastAPI as the REST layer
The pipeline includes a human-in-the-loop approval gate with automatic escalation — a pattern aligned with enterprise governance requirements and EU AI Act human oversight provisions.

Architecture Overview

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌─────────────────────────────────┐
│ FastAPI / REST Layer │
│ POST /travel/submit │
└───────────────┬─────────────────┘
│
┌───────────────▼─────────────────┐
│ Orchestrator Agent │
│ Routing · State · Escalation │
└──┬──────────────────────────┬───┘
│ │
┌────────────▼────┐ ┌───────────▼──────────┐
│ Policy Agent │ │ Budget Agent │
│ RAG + Rules │ │ RDS Aurora · Holds │
└────────────┬────┘ └───────────┬──────────┘
│ │
└──────────┬───────────────┘
│
┌─────────────▼───────────────────┐
│ Booking Agent │
│ Lambda GDS · DynamoDB │
└─────────────┬───────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────────▼──────┐ ┌────────▼───────┐ ┌──────▼──────────┐
│ Approval Agent │ │ Auto-Approve │ │ VP Escalation │
│ SES · SNS │ │ (low-value) │ │ (>$5,000) │
└──────────┬──────┘ └────────────────┘ └─────────────────┘
│
┌──────────▼──────────────────────────────────────────┐
│ Notification Agent │
│ SES · SNS · PDF Itinerary · Calendar Invite (.ics) │
└─────────────────────────────────────────────────────┘

The Five Agents

AgentRoleAWS Services
Orchestrator AgentRoutes, coordinates, and escalatesDynamoDB, SNS
Policy Compliance AgentValidates against corporate travel policyS3 (policy corpus), DynamoDB
Budget Validation AgentChecks department budget availabilityRDS Aurora, DynamoDB
Booking AgentSearches GDS, ranks options, creates bookingsLambda, DynamoDB
Approval AgentManager approval workflow with escalationSES, SNS, DynamoDB
Notification AgentSES emails, SNS alerts, PDF, calendarSES, SNS, Lambda, S3

Why Strands Agents SDK?

Strands gives each agent a clean, Pythonic tool interface. No boilerplate JSON schema — the SDK infers parameter types from Python type hints and docstrings:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from strands import Agent, tool
from strands.models import BedrockModel

@tool
def check_advance_booking(travel_date: str, request_date: str, destination_type: str) -> dict:
"""
Checks if the request was made with sufficient advance notice.
Min 7 days domestic, 14 days international.
"""

...

agent = Agent(
model=BedrockModel(model_id="us.amazon.nova-pro-v1:0", region_name="us-east-1"),
system_prompt=POLICY_SYSTEM_PROMPT,
tools=[check_advance_booking, check_cabin_class, check_hotel_rate_cap, ...],
name="policy_compliance_agent",
)
Each agent is self-contained with its own system prompt, tool list, and responsibility boundary. The Orchestrator wires them together.

Pipeline Deep Dive

Stage 1 — Policy Compliance Agent

The Policy Agent validates four dimensions against the corporate travel policy:
1. Advance Booking Check
1
2
3
4
5
6
7
8
@tool
def check_advance_booking(travel_date: str, request_date: str, destination_type: str) -> dict:
"""Min 7 days domestic, 14 days international."""
days_advance = (travel_date_parsed - request_date_parsed).days
required = 14 if destination_type == "international" else 7
if days_advance < required:
return {"check_result": "FAIL", "days_advance": days_advance, ...}
return {"check_result": "PASS", "days_advance": days_advance}
2. Cabin Class Validation
1
2
3
4
5
6
CABIN_RULES = {
"IC": {"economy", "premium_economy", "business"},
"Director": {"economy", "premium_economy", "business"},
"Manager": {"economy", "premium_economy"},
"Analyst": {"economy"},
}
Business class is permitted for Director+ on flights over 6 hours — a common corporate policy.
3. Hotel Rate Cap
DestinationCap
Domestic$150/night
International$250/night
4. Destination Restrictions
Sanctioned countries return BLOCKED. High-risk destinations trigger VP approval. The agent aggregates all checks into a compliance report:
1
compliance_status: COMPLIANT | NON_COMPLIANT | REQUIRES_EXCEPTION

Stage 2 — Budget Validation Agent

The Budget Agent integrates with RDS Aurora in real time to check department travel budgets:
1
2
3
4
5
6
7
8
9
10
11
@tool
def get_department_budget(department: str, fiscal_year: int) -> dict:
"""Queries RDS Aurora for total_budget, ytd_spend, and soft_holds."""
result = rds_data.execute_statement(
resourceArn=RDS_ARN, secretArn=SECRET_ARN, database=DATABASE,
sql="""SELECT total_budget, ytd_spend, soft_holds
FROM department_budgets WHERE department=:d AND fiscal_year=:y"""
,
...
)
available = total_budget - ytd_spend - soft_holds
return {"available_budget": available, ...}
Cost Estimation Formula:
1
2
3
4
total_cost = flight_cost
+ (nightly_rate × nights)
+ (per_diem_meals + per_diem_incidentals) × travel_days
+ ground_transport (10% of flight if not specified)
Budget Decision Logic:
VarianceStatus
Available ≥ EstimatedAPPROVED
Over by ≤ 20%WARN (CFO note required)
Over by > 20%REJECTED
When approved, the agent places a soft budget hold in DynamoDB — a reservation that prevents double-spending while the request awaits manager approval.

Stage 3 — Booking Agent

The Booking Agent integrates with the corporate GDS (Global Distribution System) via AWS Lambda and ranks options using a value scoring algorithm:
1
2
3
4
5
def flight_score(f):
price_score = (1 - f["price_usd"] / 5000) * 40 # 40% weight: cost
preferred = 20 if f["preferred_airline"] else 0 # 20%: negotiated rates
conn_score = (2 - f["connections"]) * 15 # 30%: direct preferred
return price_score + preferred + conn_score
The agent selects the top-ranked option and preserves 2–3 alternatives in the DynamoDB booking record for manager review. On approval, it submits to the GDS asynchronously via Lambda:
1
2
3
4
5
lambda_client.invoke(
FunctionName="travel-gds-booking-submit",
InvocationType="Event", # fire-and-forget
Payload=json.dumps({...}),
)

Stage 4 — Human-in-the-Loop Approval Gate

The Orchestrator applies tiered approval routing:
1
2
3
4
# Auto-approve: domestic, economy, ≤$500
# Manager approval: $500–$2,000 or business class
# Manager + Finance: international travel
# VP approval: > $5,000
The Approval Agent sends one-click approve/reject emails via SES with embedded action URLs:
1
2
approve_url = f"https://travel.company.com/approve/{approval_id}?action=approve"
reject_url = f"https://travel.company.com/approve/{approval_id}?action=reject"
Escalation ladder:
  • No response in 24h → escalate to manager's manager
  • No response in 48h → escalate to VP with urgent flag
  • No response in 72h → auto-approve with full audit note
If rejected, the Budget Agent's soft hold is automatically released.

Stage 5 — Notification Agent

The Notification Agent dispatches context-aware communications:
1
2
3
4
5
6
7
8
9
@tool
def create_calendar_invite(employee_email, destination, departure_date, return_date, booking_id):
"""Generates and sends an .ics calendar file via SES."""
ics_content = f"""BEGIN:VCALENDAR
...
SUMMARY:Business Travel — {destination} [{booking_id}]
DTSTART;VALUE=DATE:{departure_date.replace('-', '')}
...
END:VCALENDAR"""
On approval, the agent:
  1. Sends a confirmation email with the full itinerary
  2. Generates a PDF via Lambda → S3
  3. Sends a .ics calendar invite
  4. Notifies Finance via SNS with cost centre and budget code

Deployment

1
2
3
4
5
# agentcore_handler.py (also in main.py)
def handler(event: dict, context) -> dict:
payload = event.get("travel_request", event)
result = orchestrate_travel_request(payload)
return {"statusCode": 200, "body": json.dumps(result)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Submit a travel request
curl -X POST https://your-api/travel/submit \
-H "Content-Type: application/json" \
-d '{
"employee_id": "EMP-001",
"employee_name": "Sweta Jha",
"employee_grade": "Manager",
"origin": "BLR",
"destination": "Amsterdam",
"destination_country": "NL",
"travel_date": "2025-08-15",
"return_date": "2025-08-20",
"nights": 5,
"cabin_class": "economy",
"estimated_cost": 2400.00,
"nightly_hotel_rate": 180.00,
"purpose": "AWS Summit Amsterdam"
}'

AWS Infrastructure Summary

ResourcePurpose
Amazon Bedrock Nova ProLLM for all five agents
RDS Aurora (PostgreSQL)Department budgets + booking records
DynamoDBPipeline state, approvals, budget holds, audit log
AWS LambdaGDS flight/hotel search + PDF generation
Amazon SESEmployee + manager emails, calendar invites
Amazon SNSFinance alerts, escalation notifications
Amazon S3Policy document corpus, PDF itineraries
AgentCore RuntimeServerless agent deployment

Key Design Patterns

Soft Budget Holds — Reserve budget as soon as a request enters the pipeline, not when it's approved. Prevents over-commitment across simultaneous requests.
Tiered Auto-Approval — Low-value, policy-compliant requests bypass human review entirely. Only genuinely high-risk requests consume manager attention.
Escalation with SLA — Every pending approval has a countdown. The system never blocks indefinitely — it escalates automatically and documents every step.
Tool-First Agent Design — Every capability is a testable @tool function. Agents stay thin; business logic stays in tools.
Idempotent Operations — Budget holds and booking records use unique IDs. Retries are safe.

Try It Yourself

1
2
3
4
git clone https://github.com/its-me-sweta/Travel_Request_AI_Assistant.git
cd travel-request-assistant
pip install -r requirements.txt
uvicorn main:app --reload

Built with Strands Agents SDK · Amazon Bedrock Nova Pro · AgentCore Runtime
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article