An AI-powered personalized travel planning system that dynamically generates itineraries, optimizes bookings, and assists travelers in real-time using multi-agent coordination.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AI Travel Planner β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββ A2A Protocol βββββββββββββ β
β β CrewAI βββββββββββββββββββββββββββββββΊβ ADK β β
β β Agent β (HMAC-signed messages) β Agent β β
β ββββββββ¬ββββββββ βββββββ¬ββββββ β
β β β β
β β β β
β ββββββββββββββββ βββββββββββββββ β
β β β β
β βββββββΌβββββββββββββββΌββββββ β
β β State Store (In-Mem) β β
β ββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β External Integrations (MCP) β β
β ββββββββββββββββ¬βββββββββββββββββ¬βββββββββββββββββββββββ€ β
β β Groq Client β Gemini Flash β DuckDuckGo Search β β
β ββββββββββββββββ΄βββββββββββββββββ΄βββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Monitoring & Observability β β
β ββββββββββββββββ¬βββββββββββββββββ¬βββββββββββββββββββββββ€ β
β β Callbacks β JSON Logger β Event Tracing β β
β ββββββββββββββββ΄βββββββββββββββββ΄βββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
Multi-Agent System
- CrewAI Agent: Gathers travel requirements, searches for options (flights, hotels, activities), creates initial proposals
- ADK Agent: Optimizes itineraries for cost, time, and preferences; applies budget constraints
-
A2A Protocol (Agent-to-Agent)
- JSON message envelope with versioning
- HMAC-SHA256 signatures for message integrity
- In-memory adapter for local message passing
- Correlation ID propagation for distributed tracing
-
State Management
- Interface-based design (supports in-memory and Redis)
- Atomic operations for shared state
- TTL support for temporary data
-
External Tool Integration (MCP)
- Groq: Semantic search and document store
- Gemini 2.0 Flash: Text generation and NLP
- DuckDuckGo: Web search for real-time information
- Budget Calculator: Currency conversion and financial calculations
-
Monitoring & Observability
- Callback-based event system
- Structured JSON logging
- MonitoringEvent Pydantic models
- Trace and correlation ID tracking
-
Structured Outputs
- All data models use Pydantic v2
- JSON and Markdown export capabilities
- Type-safe throughout
This project implements the Model Context Protocol for standardized tool integration and discovery. MCP enables seamless communication between the planning system and external tools with:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Interactive Planner / Workflow β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β
ββββββββββΌβββββββββββ
β MCP Client β
β (Tool Registry) β
ββββββββββ¬βββββββββββ
β
βββββββββββββΌββββββββββββ¬βββββββββββββββ
β β β β
ββββββΌβββ ββββββΌβββ ββββββΌβββ ββββββΌβββ
βGemini β β Groq β βDuckDu β βBudget β
β β β LLM β β Go β β Calc β
βResearch β β Search β β β
ββββββββββ ββββββββββ ββββββββββ βββββββββ
| Tool | Module | Purpose | Input | Output |
|---|---|---|---|---|
gemini_research |
mcp_client.py |
Destination research (weather, lodging, attractions) | destination, dates, interests | research result dict |
groq_llm |
mcp_client.py |
Itinerary generation via LLM | prompt, json_mode, temperature | generated itinerary JSON |
duckduckgo_search |
mcp_client.py |
Web search for travel info | query, max_results | search results array |
calculator |
mcp_client.py |
Budget calculations & cost optimization | operation, amounts, currency | calculation result |
src/integrations/
βββ mcp_client.py # MCP client, tool definitions, registry
βββ mcp_tool_adapter.py # Tool adapters with MCP compliance
βββ gemini_research.py # Gemini research client (invoked via MCP)
βββ groq_client.py # Groq LLM client (invoked via MCP)
βββ duckduckgo_client.py # DuckDuckGo search (invoked via MCP)
βββ calculator.py # Budget calculator (invoked via MCP)
Tool Request:
{
"tool_name": "gemini_research",
"arguments": {
"destination": "Paris",
"travel_dates": {
"start_date": "2025-12-01",
"end_date": "2025-12-07"
},
"interests": ["culture", "art"]
},
"trace_id": "trace-abc123",
"correlation_id": "corr-xyz789"
}Tool Response:
{
"tool_name": "gemini_research",
"result": {
"destination": "Paris",
"weather_summary": "...",
"accommodation_suggestions": "...",
"top_attractions": "...",
"estimated_daily_cost": 150.0,
"currency": "EUR",
"travel_tips": "...",
"best_time_to_visit": "..."
},
"error": null,
"trace_id": "trace-abc123",
"correlation_id": "corr-xyz789"
}List Available Tools:
from src.integrations.mcp_client import get_mcp_client
mcp = get_mcp_client()
tools = mcp.list_tools()
for tool in tools:
print(f"{tool.name}: {tool.description}")
print(f" Category: {tool.category}")
print(f" Schema: {tool.input_schema}")Invoke a Tool:
from src.integrations.mcp_tool_adapter import invoke_mcp_tool
import uuid
response = await invoke_mcp_tool(
tool_name="gemini_research",
arguments={
"destination": "Tokyo",
"travel_dates": {
"start_date": "2025-12-20",
"end_date": "2025-12-27"
}
},
trace_id=str(uuid.uuid4()),
correlation_id=str(uuid.uuid4())
)
if response.error:
print(f"Error: {response.error}")
else:
print(f"Research: {response.result}")The A2A protocol is MCP-compliant with:
- Trace ID: Distributed tracing across tool invocations
- Correlation ID: Request correlation for multi-step operations
- Message Versioning: Ensures protocol compatibility
- HMAC Signatures: Secure tool-to-agent communication
β Requirement: At least 2 external tools integrated
- Gemini 2.0 Flash (research)
- Groq LLM (generation)
- DuckDuckGo Search (search fallback)
- Budget Calculator (optimization)
β
Tool Discovery: MCPClient.list_tools() exposes all available tools
β
Request/Response Format: Standardized MCPToolRequest / MCPToolResponse models
β Error Handling: All tools return structured error responses with trace IDs
β Async Support: All tool adapters are fully async-compatible
- Python 3.9 or higher
- pip (Python package manager)
- Clone or navigate to the repository
cd ai-travel-planner- Set up environment
On Linux/macOS:
chmod +x scripts/local_run.sh
./scripts/local_run.shOn Windows:
# Create virtual environment
python -m venv venv
# Activate virtual environment
.\venv\Scripts\Activate.ps1
# Install dependencies
pip install --upgrade pip
pip install -r requirements.txt- Configure environment variables
Copy .env.example to .env and fill in your API keys:
cp .env.example .envEdit .env with your actual API keys:
GROQ_API_KEY: Your Groq API keyGEMINI_API_KEY: Your Google Gemini API keyCREWAI_API_KEY: Your CrewAI API key (if using real CrewAI)ADK_API_KEY: Your ADK API key (if using real ADK)A2A_SHARED_SECRET: Secret for HMAC message signing (change from default!)- Other API keys as needed
Demo mode with sample request:
python -m src.main examples/sample_itinerary_request.jsonWith your own request file:
python -m src.main path/to/your/request.jsonThe application will:
- Load traveler profile and preferences
- Coordinate agents via A2A protocol
- Invoke research tool via MCP (Gemini)
- Generate itinerary via MCP (Groq LLM)
- Output both JSON and Markdown formats
- Save results to
examples/generated_itinerary.*
Use the interactive planner to be prompted for origin, destination, dates, budget, and preferences. It will then perform research, generate a proposal, optimize it, and save timestamped outputs including embedded research.
Windows PowerShell:
& .\myenv\Scripts\Activate.ps1
python -m src.interactive_plannerExample flow:
- You are prompted for trip basics (origin, destination, start/end dates, budget, interests)
- Gemini research runs (weather, lodging ranges, attractions, local tips, indicative costs)
- CrewAI planner produces an initial structured itinerary
- ADK optimizer adjusts cost/timing while preserving structure
- Time ranges are parsed into per-activity
start_time/end_timedatetimes - Files saved:
examples/itinerary_<destination>_<YYYYMMDD_HHMMSS>.json+.md, plus research markdown
Outputs now include:
- Embedded research block in both JSON and Markdown
- "At-a-Glance" summary (days, total estimated spend, primary themes)
- "Top Attractions" highlight list
- Accurate daily activity time ranges (no longer all midnight)
- Consistent cost breakdown with total propagated through optimization
If you cancel mid-run, partial research may still save; rerun to regenerate a full itinerary.
pytestpytest --cov=src --cov-report=htmlpytest src/tests/test_models.py
pytest src/tests/test_a2a_protocol.py
pytest src/tests/test_integration.py- β Pydantic model validation and serialization
- β A2A HMAC signature signing and verification
- β State store operations (set, get, delete, list)
- β Monitoring callbacks and event emission
- β End-to-end workflow integration
ai-travel-planner/
βββ .env # Environment variables (gitignored)
βββ .env.example # Environment template
βββ .gitignore # Git ignore rules
βββ pyproject.toml # Project metadata
βββ requirements.txt # Python dependencies
βββ README.md # This file
β
βββ envs/
β βββ .env.ci # CI environment variables
β
βββ src/
β βββ main.py # Application entry point
β β
β βββ config/
β β βββ settings.py # Pydantic settings
β β
β βββ models/
β β βββ itinerary.py # All Pydantic models
β β
β βββ a2a/
β β βββ protocol.py # A2A message protocol
β β βββ adapters/
β β βββ in_memory.py # In-memory message adapter
β β
β βββ state/
β β βββ store.py # State store interface & impl
β β
β βββ integrations/
β β βββ groq_client.py # Groq API wrapper
β β βββ gemini_flash_client.py # Gemini API wrapper
β β βββ duckduckgo_client.py # DuckDuckGo wrapper
β β βββ calculator.py # Currency & budget utils
β β
β βββ agents/
β β βββ crewai_agent/
β β β βββ agent.py # CrewAI agent wrapper
β β β βββ handlers.py # Lifecycle handlers
β β βββ adk_agent/
β β βββ agent.py # ADK agent wrapper
β β
β βββ callbacks/
β β βββ monitoring.py # Monitoring callbacks
β β βββ logger_adapter.py # Logger adapter
β β
β βββ logging/
β β βββ json_logger.py # Structured JSON logger
β β
β βββ workflows/
β β βββ dynamic_planner.py # Workflow orchestration
β β
β βββ tests/
β βββ conftest.py # Test configuration
β βββ test_models.py # Model tests
β βββ test_a2a_protocol.py # A2A protocol tests
β βββ test_state_store.py # State store tests
β βββ test_callbacks.py # Callback tests
β βββ test_integration.py # Integration tests
β
βββ examples/
β βββ sample_itinerary_request.json # Sample input
β βββ sample_a2a_trace.json # Sample A2A trace
β βββ generated_itinerary.json # Generated output (JSON)
β βββ generated_itinerary.md # Generated output (Markdown)
β
βββ scripts/
β βββ local_run.sh # Local development script
β βββ db_migrate.py # Database migration stub
β βββ seed_demo_data.py # Demo data seeder
β
βββ ops/
βββ commit_history_example.txt # Sample commit history
### Output Naming Pattern
Generated itinerary and research files follow:
itinerary_<YYYYMMDD_HHMMSS>.json itinerary<YYYYMMDD_HHMMSS>.md research_<YYYYMMDD_HHMMSS>.md
Ensures no overwrites across runs.
### Recent Enhancements
- Time Range Parsing: Activities now reflect scheduled ranges like "09:00 AM - 11:00 AM" instead of defaulting to midnight. Duration is computed as the difference between parsed start and end times.
- Embedded Research: Weather, lodging ranges, attraction summaries, local tips, and indicative costs are stored alongside itinerary output (JSON + Markdown).
- At-a-Glance & Highlights: Quick summary section plus top attractions list in Markdown for rapid scanning.
- Optimization Routing Fixes: ADK optimized plan now reliably stored/retrieved via multiple state keys fallback.
- Serialization Improvements: Robust handling of `Decimal` and `datetime` objects in prompts and outputs.
- Unique Filenames: Timestamp + destination prevents accidental overwrites during iterative planning.
- Resilient JSON Parsing: Fallback logic handles truncated or malformed LLM JSON responses without losing prior valid data.
### Time Parsing Details
The workflow attempts to parse activity time strings of the form:
HH:MM AM - HH:MM PM
If parsing fails, a safe fallback window (09:00β10:00) is used and logged. Activities are anchored to `start_date + (day_index)` so day offsets are respected.
### Monitoring Log Warning (FYI)
If you see repeated messages like:
Attempt to overwrite 'message' in LogRecord
This stems from a logger adapter assigning `message` explicitly. It is cosmetic; to silence it, adjust the adapter to use a different key (e.g., `original_message`) or avoid overriding `record.message`.
### Planned Next Steps (Suggested)
- Suppress cosmetic logging warnings
- Add JSON highlights array mirroring Markdown top attractions
- Improve LLM JSON schema validation (streamed chunk assembly)
- Add currency conversion for per-day spend vs total
- Optional Redis state backend for multi-session continuity
---
CRITICAL: Never commit .env files to version control!
.envis in.gitignoreby default- Use
.env.exampleas a template - Store sensitive keys in secure vaults (e.g., AWS Secrets Manager, Azure Key Vault)
- All A2A messages are HMAC-signed using
A2A_SHARED_SECRET - Change the default secret in production
- Use strong, randomly generated secrets (32+ characters)
- Rotate secrets periodically
- Replace all placeholder API keys before production use
- Use different keys for dev/test/prod environments
- Monitor API usage for anomalies
- Set up rate limiting and budget alerts
On Linux/macOS, set restrictive permissions:
chmod 600 .env| Variable | Required | Default | Description |
|---|---|---|---|
APP_ENV |
No | development |
Environment name |
LOG_LEVEL |
No | INFO |
Logging level |
SECRET_KEY |
Yes | - | Application secret key |
A2A_SHARED_SECRET |
Yes | - | A2A HMAC signing secret |
GROQ_API_KEY |
No | - | Groq API key |
GEMINI_API_KEY |
No | - | Gemini API key |
GEMINI_MODEL |
No | gemini-2.0-flash |
Gemini model name |
CREWAI_API_KEY |
No | - | CrewAI API key |
ADK_API_KEY |
No | - | ADK API key |
STATE_BACKEND |
No | inmemory |
State backend (inmemory/redis) |
REDIS_URL |
No | - | Redis connection URL |
ENABLE_MONITORING |
No | true |
Enable monitoring |
ALLOW_BOOKING_OPERATIONS |
No | false |
Allow real bookings |
DEFAULT_CURRENCY |
No | USD |
Default currency |
BUDGET_ALERT_THRESHOLD |
No | 0.9 |
Budget alert threshold |
{
"message_id": "unique-uuid",
"trace_id": "distributed-trace-id",
"correlation_id": "request-correlation-id",
"message_type": "proposal|optimized_plan|query|response|error",
"version": "1.0",
"timestamp": "2025-11-18T10:30:00Z",
"payload": {
"...message-specific-data..."
},
"meta": {
"sender": "agent-id",
"receiver": "agent-id",
"priority": 5,
"ttl": 300
},
"signature": "hmac-sha256-hex-signature"
}- proposal: Initial travel proposal from CrewAI agent
- optimized_plan: Optimized plan from ADK agent
- query: Request for information
- response: Response to query
- error: Error notification
Messages are signed using HMAC-SHA256:
from a2a.protocol import sign_message, verify_message
# Sign
signed_msg = sign_message(message)
# Verify
is_valid = verify_message(signed_msg)- Replace stub implementation in
src/integrations/groq_client.py - Add actual endpoint URLs and authentication
- Implement retry logic for production
- Uses
google-generativeaipackage (not installed by default) - Set
GEMINI_API_KEYin environment - Supports both synchronous and streaming modes
- Public HTML interface (no API key required)
- Consider using
duckduckgo-searchpackage for production - Alternative: Use Bing or Google Custom Search APIs
- Current implementation uses stubs
- Install actual packages when available:
pip install crewai pip install adk
- Update agent wrappers with real API calls
- Create agent directory:
src/agents/new_agent/ - Implement agent class with A2A support
- Register with workflow orchestrator
- Add tests
- Create client wrapper in
src/integrations/ - Implement typed request/response models
- Add retry and error handling
- Write unit tests
Implement the StateStore interface:
from state.store import StateStore
class CustomStateStore(StateStore):
async def get(self, key: str) -> Optional[Any]: ...
async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool: ...
# ... implement other methodsLogs are written in JSON format with correlation IDs:
{
"timestamp": "2025-11-18T10:30:00Z",
"level": "INFO",
"logger": "src.workflows.dynamic_planner",
"message": "Starting planning workflow",
"trace_id": "trace-abc",
"correlation_id": "corr-xyz",
"task_id": "task-001"
}Events are emitted to monitoring_events.json:
{
"event_id": "evt-001",
"event_type": "task_start",
"severity": "info",
"trace_id": "trace-abc",
"correlation_id": "corr-xyz",
"task_id": "task-001",
"agent_id": "crewai-planner",
"message": "Task started"
}Import errors:
# Ensure you're in the project root and virtual environment is activated
python -m src.main examples/sample_itinerary_request.jsonMissing API keys:
- Check
.envfile exists and has correct values - Stub implementations work without real API keys
Tests failing:
# Ensure test dependencies are installed
pip install -r requirements.txtState store errors:
- Default is in-memory (no external dependencies)
- For Redis, ensure Redis server is running
All activity times show 12:00 AM:
- Ensure you are running the updated workflow with time range parsing (post-enhancement). Reinstall dependencies and rerun
python -m src.interactive_planner. - Verify the LLM is returning time ranges in the daily schedule (enable debug logging if needed).
Missing research in JSON:
- Confirm interactive mode was used (non-interactive main may exclude certain embedding steps depending on version).
- Ensure environment variable
ENABLE_MONITORING=truedoes not interfere (it should not, but check logs for early exceptions).
MIT License - see LICENSE file for details
This is a demonstration/scaffold project. In production:
- Replace stub implementations with real API calls
- Add comprehensive error handling
- Implement rate limiting
- Add authentication and authorization
- Set up CI/CD pipelines
- Add monitoring and alerting
- Implement database persistence
- Add caching layer
- Implement booking confirmation workflows
- Add user interface (web/mobile)
For issues and questions, please check:
- Project documentation
- Test files for usage examples
- Code comments and docstrings
Note: This is a skeleton implementation for demonstration purposes. External API calls are stubbed and will return mock data unless you provide valid API keys and update the client implementations.