Use this file to discover all available pages before exploring further.
Build an advanced trip planning agent that integrates Google Maps, weather forecasts, and iterative user conversations to create personalized travel itineraries.
package ai.koog.agents.examples.tripplanningimport ai.koog.agents.examples.tripplanning.api.OpenMeteoClientimport ai.koog.agents.mcp.McpToolRegistryProviderimport ai.koog.prompt.executor.clients.anthropic.AnthropicLLMClientimport ai.koog.prompt.executor.clients.google.GoogleLLMClientimport ai.koog.prompt.executor.clients.openai.OpenAILLMClientimport ai.koog.prompt.executor.llms.MultiLLMPromptExecutorimport ai.koog.prompt.llm.LLMProviderimport io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransportimport kotlinx.coroutines.delayimport kotlinx.datetime.Clockimport kotlinx.datetime.TimeZoneimport kotlinx.datetime.toLocalDateTimesuspend fun main() { val openAiKey = System.getenv("OPENAI_API_KEY") val anthropicKey = System.getenv("ANTHROPIC_API_KEY") val googleAiKey = System.getenv("GOOGLE_AI_API_KEY") val googleMapsKey = System.getenv("GOOGLE_MAPS_API_KEY") val googleMapsMcp = createGoogleMapsMcp(googleMapsKey) try { // Create agent val agent = createPlannerAgent( promptExecutor = MultiLLMPromptExecutor( LLMProvider.OpenAI to OpenAILLMClient(openAiKey), LLMProvider.Anthropic to AnthropicLLMClient(anthropicKey), LLMProvider.Google to GoogleLLMClient(googleAiKey) ), openMeteoClient = OpenMeteoClient(), googleMapsMcpRegistry = McpToolRegistryProvider.fromTransport(googleMapsMcp), onToolCallEvent = { println("Tool called: $it") }, showMessage = { println("Agent: $it") print("Response > ") readln() } ) // Get initial request println("Hi, I'm a trip planner agent. Tell me where and when do you want to go, and I'll help you prepare the plan.") print("Response > ") val message = readln() val timezone = TimeZone.currentSystemDefault() val userInput = UserInput( message = message, currentDate = Clock.System.now().toLocalDateTime(timezone).date, timezone = timezone, ) // Print final result val result: TripPlan = agent.run(userInput) println(result.toMarkdownString()) } finally { // Don't forget to close MCP transport after use googleMapsMcp.close() }}private suspend fun createGoogleMapsMcp(googleMapsKey: String): StdioClientTransport { // Start MCP server val process = ProcessBuilder( "docker", "run", "-i", "-e", "GOOGLE_MAPS_API_KEY=$googleMapsKey", "mcp/google-maps" ).start() // Wait for the MCP server to boot delay(1000) // Create transport to MCP return McpToolRegistryProvider.defaultStdioTransport(process)}
The core strategy uses subgraphs and conditional edges:
Planning Strategy
private fun plannerStrategy( googleMapsTools: List<Tool<*, *>>, addDateTool: Tool<*, *>, weatherTools: WeatherTools, userTools: UserTools) = strategy<UserInput, TripPlan>("planner-strategy") { val userPlanKey = createStorageKey<TripPlan>("user_plan") val prevSuggestedPlanKey = createStorageKey<TripPlan>("prev_suggested_plan") // Nodes val setup by node<UserInput, String> { userInput -> llm.writeSession { updatePrompt { system { +"Today's date is ${userInput.currentDate}." } } } userInput.message } val clarifyUserPlan by subgraphWithTask<String, TripPlan>( tools = userTools.asTools() + addDateTool ) { initialMessage -> xml { tag("instructions") { +""" Clarify a user plan until the locations, dates and additional information, such as user preferences, are provided. """.trimIndent() } tag("initial_user_message") { +initialMessage } } } val suggestPlan by subgraphWithTask<SuggestPlanRequest, TripPlan>( tools = googleMapsTools + weatherTools.asTools() ) { input -> xml { tag("instructions") { markdown { h2("Requirements") bulleted { item("Suggest the plan for ALL days and ALL locations in the user plan, preserving the order.") item("Follow the user plan and provide a detailed step-by-step plan suggestion with multiple options for each date.") item("Consider weather conditions when suggesting places for each date and time to assess how suitable the activity is for the weather.") item("Check detailed information about each place, such as opening hours and reviews, before adding it to the final plan suggestion.") } h2("Tool usage guidelines") +""" ALWAYS use "maps_search_places" tool to search for places, AVOID making your own suggestions. While searching for places, keep search query short and specific: Example DO: "museum", "historical museum", "italian restaurant", "coffee shop", "art gallery" Example DON'T: "interesting cultural sites", "local cuisine restaurants", "restaurant in the city center" """.trimIndent() } } when (input) { is SuggestPlanRequest.InitialRequest -> { tag("user_plan") { +input.userPlan.toMarkdownString() } } is SuggestPlanRequest.CorrectionRequest -> { tag("additional_instructions") { +"User asked for corrections to the previously suggested plan. Provide updated plan according to these corrections." } tag("user_plan") { +input.userPlan.toMarkdownString() } tag("previously_suggested_plan") { +input.prevSuggestedPlan.toMarkdownString() } tag("user_feedback") { +input.userFeedback } } } } } val saveUserPlan by node<TripPlan, Unit> { plan -> storage.set(userPlanKey, plan) llm.writeSession { replaceHistoryWithTLDR(strategy = HistoryCompressionStrategy.WholeHistory) } } val savePrevSuggestedPlan by node<TripPlan, TripPlan> { plan -> storage.set(prevSuggestedPlanKey, plan) llm.writeSession { replaceHistoryWithTLDR(strategy = HistoryCompressionStrategy.WholeHistory) } plan } val showPlanSuggestion by node<String, String> { message -> userTools.showMessage(message) } val processUserFeedback by nodeLLMRequestStructured<PlanSuggestionFeedback>() // Edges - define the flow nodeStart then setup then clarifyUserPlan then saveUserPlan edge( savePrevSuggestedPlan forwardTo showPlanSuggestion transformed { it.toMarkdownString() } ) edge(showPlanSuggestion forwardTo processUserFeedback) // Feedback loop: if not accepted, iterate edge( processUserFeedback forwardTo createPlanCorrectionRequest transformed { it.getOrThrow().structure } onCondition { !it.isAccepted } transformed { it.message } ) // If accepted, finish edge( processUserFeedback forwardTo nodeFinish transformed { it.getOrThrow().structure } onCondition { it.isAccepted } transformed { storage.getValue(prevSuggestedPlanKey) } ) edge(createPlanCorrectionRequest forwardTo suggestPlan)}
Agent: Hi, I'm a trip planner agent. Tell me where and when do you want to go...You: I want to visit Paris for 3 days starting next MondayAgent: What are your interests? (museums, food, nightlife, etc.)You: Museums and good restaurants[Agent searches for places and checks weather]Agent: Here's your suggested itinerary...[Displays detailed plan]Agent: Would you like to make any changes?You: Can you add more time for the Louvre?[Agent refines the plan]