A travel planning agent built with llm4s and workflows4s, demonstrating functional programming patterns for agentic AI applications.
llm4s-tripper is a Scala 3 application that creates personalized travel itineraries using:
- llm4s: Type-safe LLM interactions with structured outputs
- workflows4s: Deterministic workflow orchestration with WIO composition
- Cats Effect: Functional effect system for IO and concurrency
- MCP (Model Context Protocol): Real-time data from Google Maps, Brave Search, and more
| Component | Status |
|---|---|
| Workflow Engine | ✅ Complete - workflows4s WIO composition |
| LLM Integration | ✅ Complete - OpenAI supported |
| MCP Integration | ✅ Complete - Real POI discovery |
| CLI Interface | ✅ Complete |
| Tests | ✅ 329 tests passing |
See docs/IMPLEMENTATION_STATUS_REPORT.md for detailed status.
The application follows a feature-based package organization:
io.github.llm4s.tripper/
├── cli/ # Command-line interface
├── config/ # Configuration (PureConfig)
├── domain/ # Domain model (TravelBrief, POI, TravelPlan)
├── llm/ # LLM client factory and configuration
├── logging/ # Structured logging with log4cats
├── mcp/ # MCP client manager, tool registry, and rate limiting
└── workflow/ # Workflow definitions and steps
├── runtime/ # WorkflowRuntimeFactory, InstanceManager
└── steps/ # POI Discovery, Research, Plan Generation, etc.
The travel planning workflow uses workflows4s WIO composition:
POI Discovery >>> POI Research >>> Plan Generation >>> Accommodation Discovery >>> Plan Finalization
Each step is a WIO[State, Error, State] composed using the >>> operator.
- Functional Core, Imperative Shell: Pure domain logic with effects at the edges
- Type-Safe LLM Interactions: Circe-based JSON encoding/decoding for structured outputs
- WIO Workflow Composition: workflows4s
>>>operator for step sequencing - Parallel Execution: Cats Effect
parTraversewith Semaphore for concurrency control - StepProvider Abstraction: Switch between real MCP and mock implementations
- Configuration as Code: PureConfig with environment variable overrides
- JDK 21+
- sbt 1.11.7+
- Scala 3.7.3
- Docker Desktop (for real MCP integration)
$HOME/.docker-java.propertieswithapi.version=1.44
# LLM Provider (required)
export OPENAI_API_KEY=your_openai_api_key
# For real MCP integration (optional)
export GOOGLE_MAPS_API_KEY=your_google_maps_api_key
export BRAVE_API_KEY=your_brave_search_api_key# Compile the project
sbt compile
# Run tests (329 tests)
sbt test
# Run with real MCP tools (default - requires API keys)
sbt "runMain io.github.llm4s.tripper.Main plan -d Paris,France -s 2025-06-01 -e 2025-06-05 \
-p art,history -n JohnDoe -i museums,architecture -o paris-trip.json"
# Run with mock data (for development/testing without API keys)
sbt "runMain io.github.llm4s.tripper.Main plan -d Paris,France -s 2025-06-01 -e 2025-06-05 \
-p art,history -n JohnDoe -i museums,architecture --useMockData -o paris-trip.json"The application provides a command-line interface for planning trips.
# Real MCP mode (default) - uses Google Maps, Brave Search, etc.
sbt "runMain io.github.llm4s.tripper.Main plan -d Paris,France -s 2025-06-01 -e 2025-06-05 \
-p art,history -n JohnDoe -i museums,architecture -o my-paris-trip.json"
# Mock mode - for development without external API calls
sbt "runMain io.github.llm4s.tripper.Main plan -d Paris,France -s 2025-06-01 -e 2025-06-05 \
-p art,history -n JohnDoe -i museums,architecture --useMockData -o my-paris-trip.json"# Create trip-request.json:
{
"from": "New York, USA",
"to": "Paris, France",
"startDate": "2025-06-01",
"endDate": "2025-06-05",
"travelers": [
{
"name": "John Doe",
"interests": ["art", "history"],
"dietaryRestrictions": [],
"mobilityConsiderations": null
}
]
}
# Run:
sbt "runMain io.github.llm4s.tripper.Main planFromJson -i trip-request.json -o paris-plan.json"sbt "runMain io.github.llm4s.tripper.Main plan -d Rome,Italy -s 2025-07-01 -e 2025-07-07 \
-p history,food -n JaneSmith -i ancient-ruins,cuisine -v -o rome-trip.json"| Command | Description |
|---|---|
plan |
Plan a trip using command-line arguments |
planFromJson |
Plan a trip using a JSON input file |
version |
Display version information |
help |
Show usage help |
| Option | Short | Description |
|---|---|---|
--destination |
-d |
Destination (required) |
--startDate |
-s |
Start date YYYY-MM-DD (required) |
--endDate |
-e |
End date YYYY-MM-DD (required) |
--preferences |
-p |
Comma-separated preferences |
--name |
-n |
Traveler name |
--interests |
-i |
Comma-separated interests |
--output |
-o |
Output file (default: travel-plan.json) |
--verbose |
-v |
Enable verbose logging |
--useMockData |
Use mock data instead of real MCP (for dev/testing) |
llm4s-tripper supports two execution modes controlled by the --useMockData flag:
Uses real MCP tools for POI discovery, research, and accommodation search. Requires API keys.
# Default - uses real MCP tools
sbt "runMain io.github.llm4s.tripper.Main plan -d Paris,France -s 2025-06-01 -e 2025-06-05 \
-p art,history -n JohnDoe -i museums,architecture -o paris-trip.json"What happens in real mode:
- POI Discovery: Calls Google Maps MCP server for real places
- POI Research: Uses Brave Search and Wikipedia for detailed info
- Plan Generation: LLM creates itinerary from real data
- Accommodation: Searches real listings (with fallback to mock)
For development and testing without external API calls. Still requires OPENAI_API_KEY for LLM calls.
# Use --useMockData flag
sbt "runMain io.github.llm4s.tripper.Main plan -d Paris,France -s 2025-06-01 -e 2025-06-05 \
-p art,history -n JohnDoe -i museums,architecture --useMockData -o paris-trip.json"What happens in mock mode:
- POI Discovery: LLM generates POIs without MCP
- POI Research: LLM researches without external tools
- Plan Generation: LLM creates itinerary
- Accommodation: Returns hardcoded mock data
The application uses native MCP clients with stdio transport:
┌─────────────────┐ stdio ┌─────────────────┐
│ llm4s-tripper │ ◄────────────► │ MCP Server │
│ (MCPClient) │ JSON-RPC │ (Docker) │
└─────────────────┘ └─────────────────┘
| Service | Purpose | API Key Required |
|---|---|---|
google-maps |
POI discovery, geocoding | GOOGLE_MAPS_API_KEY |
brave-search |
Web search for research | BRAVE_API_KEY |
wikipedia |
Detailed information | None |
openbnb-airbnb |
Accommodation listings | None |
# Docker images are pulled automatically by llm4s MCP client
# The following servers are configured:
# - google-maps (requires GOOGLE_MAPS_API_KEY)
# - brave-search (requires BRAVE_API_KEY)
# - wikipedia (no API key required)
# - openbnb-airbnb (no API key required)
# Set API keys
export GOOGLE_MAPS_API_KEY=your_key
export BRAVE_API_KEY=your_key
export OPENAI_API_KEY=your_keyZero POIs discovered:
- Verify
GOOGLE_MAPS_API_KEYhas Places API (New) enabled - Check Docker Desktop is running
- Use
-vflag for verbose logging
Connection errors:
- Ensure Docker images are pulled
- Check
$HOME/.docker-java.propertieshasapi.version=1.44
Configuration is managed through application.conf in src/main/resources/.
tripper {
llm {
planner {
provider = "openai"
provider = ${?LLM_PLANNER_PROVIDER}
model = "gpt-4.1"
model = ${?LLM_PLANNER_MODEL}
temperature = 0.7
max-tokens = 4000
timeout = 60s
}
researcher {
provider = "openai"
model = "gpt-4.1-nano"
temperature = 0.5
max-tokens = 2000
timeout = 30s
}
}
}| Variable | Description |
|---|---|
OPENAI_API_KEY |
OpenAI API key (required) |
LLM_PLANNER_PROVIDER |
LLM provider for planner role |
LLM_PLANNER_MODEL |
Model for planner role |
LLM_PLANNER_TIMEOUT |
Timeout for planner calls |
LLM_RESEARCHER_PROVIDER |
LLM provider for researcher role |
LLM_RESEARCHER_MODEL |
Model for researcher role |
GOOGLE_MAPS_API_KEY |
Google Maps API key |
BRAVE_API_KEY |
Brave Search API key |
MCP_TOOLS_BRAVE_MIN_INTERVAL |
Rate limit interval for Brave search (default: 1s) |
MCP_TOOLS_WIKIPEDIA_MIN_INTERVAL |
Rate limit interval for Wikipedia (default: 200ms) |
| Library | Version | Purpose |
|---|---|---|
| workflows4s | local | Workflow orchestration (WIO) |
| llm4s | local | LLM integration |
| Cats Effect | 3.6.3 | Effect system |
| Cats Retry | 3.1.0 | Retry policies |
| http4s | 0.23.31 | HTTP client |
| Circe | 0.14.10 | JSON handling |
| PureConfig | 0.17.8 | Configuration |
| log4cats | 2.7.0 | Logging |
| mainargs | 0.7.6 | CLI parsing |
| Document | Description |
|---|---|
| IMPLEMENTATION_STATUS_REPORT.md | Current implementation status |
| IMPLEMENTATION_PLAN.md | Original implementation plan |
| WISHLIST.md | Future features (Anthropic, etc.) |
| REAL_TRIP_PLANNING_IMPLEMENTATION.md | Detailed task documentation |
MIT License
Copyright (c) 2025 Giovanni Ruggiero