diff --git a/demos/use_cases/travel_booking/README.md b/demos/use_cases/agent_orchestration[travel]/README.md similarity index 93% rename from demos/use_cases/travel_booking/README.md rename to demos/use_cases/agent_orchestration[travel]/README.md index 865ae228..67beec04 100644 --- a/demos/use_cases/travel_booking/README.md +++ b/demos/use_cases/agent_orchestration[travel]/README.md @@ -120,6 +120,16 @@ Assistant: [Flight information from Istanbul to Seattle] The system understands context and pronouns, automatically routing to the right agent. +### Multi-Intent Queries +``` +User: What's the weather in Seattle, and do any flights go direct to New York? +Assistant: [Both weather_agent and flight_agent respond simultaneously] + - Weather Agent: [Weather information for Seattle] + - Flight Agent: [Flight information from Seattle to New York] +``` + +The orchestrator can select multiple agents simultaneously for queries containing multiple intents. + ## Agent Details ### Weather Agent diff --git a/demos/use_cases/travel_booking/arch_config.yaml b/demos/use_cases/agent_orchestration[travel]/arch_config.yaml similarity index 66% rename from demos/use_cases/travel_booking/arch_config.yaml rename to demos/use_cases/agent_orchestration[travel]/arch_config.yaml index 43a3ea44..b2f09087 100644 --- a/demos/use_cases/travel_booking/arch_config.yaml +++ b/demos/use_cases/agent_orchestration[travel]/arch_config.yaml @@ -14,6 +14,9 @@ model_providers: - model: openai/gpt-4o-mini access_key: $OPENAI_API_KEY +system_prompt: | + You are a helpful travel assistant. You are able to answer questions about weather, flights, and currency. You are able to answer questions about the weather in a city, the flights between two cities, and the currency exchange rate between two currencies. + listeners: - type: agent name: travel_booking_service @@ -21,9 +24,9 @@ listeners: router: plano_orchestrator_v1 agents: - id: weather_agent - description: Get real-time weather conditions and multi-day forecasts for any city worldwide using Open-Meteo API (free, no API key needed). Provides current temperature, multi-day forecasts, weather conditions, sunrise/sunset times, and detailed weather information. Understands conversation context to resolve location references from previous messages. + description: Get real-time weather conditions and multi-day forecasts for any city worldwide using Open-Meteo API (free, no API key needed). Provides current temperature, multi-day forecasts, weather conditions, sunrise/sunset times, and detailed weather information. Understands conversation context to resolve location references from previous messages. Handles weather-related questions including "What's the weather in [city]?", "What's the forecast for [city]?", "How's the weather in [city]?". When queries include both weather and other travel questions (e.g., flights, currency), this agent answers ONLY the weather part. - id: flight_agent - description: Get live flight information between airports using FlightAware AeroAPI. Shows real-time flight status, scheduled/estimated/actual departure and arrival times, gate and terminal information, delays, aircraft type, and flight status. Automatically resolves city names to airport codes (IATA/ICAO). Understands conversation context to infer origin/destination from follow-up questions. Supports queries like "What flights go from London to Seattle?" or "Do they fly out from Seattle?" (using context from previous messages). + description: Get live flight information between airports using FlightAware AeroAPI. Shows real-time flight status, scheduled/estimated/actual departure and arrival times, gate and terminal information, delays, aircraft type, and flight status. Automatically resolves city names to airport codes (IATA/ICAO). Understands conversation context to infer origin/destination from follow-up questions. Handles flight-related questions including "What flights go from [city] to [city]?", "Do flights go to [city]?", "Are there direct flights from [city]?". When queries include both flight and other travel questions (e.g., weather, currency), this agent answers ONLY the flight part. - id: currency_agent description: Get real-time currency exchange rates and perform currency conversions using Frankfurter API (free, no API key needed). Provides latest exchange rates, currency conversions with amount calculations, and supports any currency pair. Automatically extracts currency codes from country names and conversation context. Understands pronouns like "their currency" when referring to previously mentioned countries. Uses standard 3-letter ISO currency codes (e.g., USD, EUR, GBP, JPY, PKR). diff --git a/demos/use_cases/travel_booking/docker-compose.yaml b/demos/use_cases/agent_orchestration[travel]/docker-compose.yaml similarity index 100% rename from demos/use_cases/travel_booking/docker-compose.yaml rename to demos/use_cases/agent_orchestration[travel]/docker-compose.yaml diff --git a/demos/use_cases/travel_booking/pyproject.toml b/demos/use_cases/agent_orchestration[travel]/pyproject.toml similarity index 100% rename from demos/use_cases/travel_booking/pyproject.toml rename to demos/use_cases/agent_orchestration[travel]/pyproject.toml diff --git a/demos/use_cases/travel_booking/src/travel_agents/__init__.py b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/__init__.py similarity index 100% rename from demos/use_cases/travel_booking/src/travel_agents/__init__.py rename to demos/use_cases/agent_orchestration[travel]/src/travel_agents/__init__.py diff --git a/demos/use_cases/travel_booking/src/travel_agents/__main__.py b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/__main__.py similarity index 100% rename from demos/use_cases/travel_booking/src/travel_agents/__main__.py rename to demos/use_cases/agent_orchestration[travel]/src/travel_agents/__main__.py diff --git a/demos/use_cases/travel_booking/src/travel_agents/api.py b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/api.py similarity index 100% rename from demos/use_cases/travel_booking/src/travel_agents/api.py rename to demos/use_cases/agent_orchestration[travel]/src/travel_agents/api.py diff --git a/demos/use_cases/travel_booking/src/travel_agents/currency_agent.py b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/currency_agent.py similarity index 100% rename from demos/use_cases/travel_booking/src/travel_agents/currency_agent.py rename to demos/use_cases/agent_orchestration[travel]/src/travel_agents/currency_agent.py diff --git a/demos/use_cases/travel_booking/src/travel_agents/flight_agent.py b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/flight_agent.py similarity index 78% rename from demos/use_cases/travel_booking/src/travel_agents/flight_agent.py rename to demos/use_cases/agent_orchestration[travel]/src/travel_agents/flight_agent.py index f16eae79..22be5d7c 100644 --- a/demos/use_cases/travel_booking/src/travel_agents/flight_agent.py +++ b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/flight_agent.py @@ -1,4 +1,5 @@ import json +import re from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse from openai import AsyncOpenAI @@ -59,26 +60,37 @@ CRITICAL INSTRUCTIONS: - Indicate flight status (scheduled, enroute, arrived, cancelled, etc.) - Show aircraft type when available -3. RESPONSE FORMAT: +3. MULTI-PART QUERIES AND MULTI-AGENT COLLABORATION: + - If the user asks multiple questions in one message (e.g., "What's the weather in Seattle, and what flights go to New York?"), focus ONLY on answering the flight-related part + - When queries contain multiple intents (weather + flights, flights + currency), you are part of a coordinated response where each agent handles their domain + - Provide complete flight information directly without mentioning other agents or deferring to them + - Example: "Here's a flight from Seattle to New York: [provide complete flight info]." (Do NOT say "other agents may handle weather") + - Do NOT attempt to answer questions outside your flight expertise (e.g., weather, currency, hotels) + - Simply provide your flight response - the system coordinates responses from multiple agents automatically + +4. RESPONSE FORMAT: - For flight searches: List available flights with clear details - Include departure and arrival times (scheduled, estimated, actual) - Mention any delays or status changes - Use natural, conversational language - Be concise but complete -4. HANDLING INCOMPLETE QUERIES: - - If the user asks a follow-up question like "Do they fly out from X?" or "Do they have flights to Y?", use conversation context to understand what they're asking - - If origin or destination is missing from the data, check if it can be inferred from the conversation context - - If you cannot determine the complete route, politely ask for clarification: "I can help you find flights! Could you please specify both the origin and destination cities? For example, 'flights from Istanbul to Seattle' or 'flights from Seattle to Istanbul'." - - When answering follow-up questions, acknowledge what you understand from context: "Based on our conversation about Istanbul, I can help you find flights from Istanbul to Seattle..." +5. HANDLING INCOMPLETE QUERIES AND CONVERSATION CONTEXT: + - ALWAYS check conversation history for context when origin or destination is missing + - If the user asks "What flights go direct from Seattle?" after asking about weather in Istanbul, infer: origin=Seattle, destination=Istanbul (from previous context) + - If the user asks "Do they fly out from X?" look for previously mentioned cities in the conversation + - When a destination is missing but a city was mentioned earlier (e.g., "What's the weather in Istanbul?"), use that city as the destination + - When an origin is missing but a city was mentioned earlier, use that city as the origin + - If you cannot determine the complete route from context, politely ask for clarification: "I can help you find flights! Could you please specify both the origin and destination cities? For example, 'flights from Istanbul to Seattle' or 'flights from Seattle to Istanbul'." + - When answering follow-up questions, acknowledge what you understand from context: "Based on our conversation about Istanbul, I can help you find flights from Seattle to Istanbul..." -5. ERROR HANDLING: +6. ERROR HANDLING: - If flight data is missing or null, acknowledge this politely - Never invent or guess flight information - only use what's provided - If airports or flights cannot be found, mention this clearly - If the route doesn't exist or no flights are available, suggest alternatives or ask if they meant a different route -Remember: Only use the data provided. Never fabricate flight information. If data is missing, clearly state what's unavailable. Use conversation context to understand follow-up questions.""" +Remember: Only use the data provided. Never fabricate flight information. If data is missing, clearly state what's unavailable. Use conversation context to understand follow-up questions. Focus ONLY on flight-related questions. Provide complete flight responses without mentioning other agents.""" FLIGHT_EXTRACTION_PROMPT = """You are a flight information extraction assistant. Your ONLY job is to extract flight-related information from user messages and convert it to structured data. @@ -106,10 +118,12 @@ CRITICAL RULES: 6. Determine the origin and destination based on context: - "from X to Y" → origin=X, destination=Y - "X to Y" → origin=X, destination=Y - - "flights from X" → origin=X, destination=null (unless context provides destination) - - "flights to Y" → origin=null (unless context provides origin), destination=Y - - "Do they fly out from X?" → origin=X (or from context), destination=from context or null - - "Do they have flights to Y?" → origin=from context, destination=Y + - "flights from X" → origin=X, destination=null (UNLESS conversation context provides a previously mentioned city - use that as destination) + - "flights to Y" → origin=null (UNLESS conversation context provides a previously mentioned city - use that as origin), destination=Y + - "What flights go direct from X?" → origin=X, destination=from conversation context (if a city was mentioned earlier) + - "Do they fly out from X?" → origin=X (or from context), destination=from context (check ALL previous messages for mentioned cities) + - "Do they have flights to Y?" → origin=from context (check ALL previous messages), destination=Y + - CRITICAL: When only one part (origin OR destination) is provided, ALWAYS check conversation history for the missing part 7. Return your response as a JSON object with the following structure: { "origin": "London" or null, @@ -128,6 +142,7 @@ CRITICAL RULES: Examples with context: - Conversation: "What's the weather in Istanbul?" → Current: "Do they fly out from Seattle?" → {"origin": "Istanbul", "destination": "Seattle", "date": null, "origin_airport_code": null, "destination_airport_code": null} +- Conversation: "What's the weather in Istanbul?" → Current: "What flights go direct from Seattle?" → {"origin": "Seattle", "destination": "Istanbul", "date": null, "origin_airport_code": null, "destination_airport_code": null} (Istanbul from previous context) - Conversation: "What's the weather in London?" → Current: "What flights go from there to Seattle?" → {"origin": "London", "destination": "Seattle", "date": null, "origin_airport_code": null, "destination_airport_code": null} - Conversation: "Tell me about Istanbul" → Current: "Do they have flights to Seattle?" → {"origin": "Istanbul", "destination": "Seattle", "date": null, "origin_airport_code": null, "destination_airport_code": null} - "What flights go from London to Seattle?" → {"origin": "London", "destination": "Seattle", "date": null, "origin_airport_code": null, "destination_airport_code": null} @@ -162,25 +177,45 @@ async def extract_flight_info_from_messages(messages): "destination_airport_code": None, } + # CRITICAL: Always preserve the FIRST user message (original query) for multi-agent scenarios + # When Plano processes multiple agents, it may add assistant responses that get filtered out, + # but we need to always use the original user query + original_user_message = user_messages[0].content.strip() if user_messages else None + + # Try to find a valid recent user message first (for follow-up queries) user_content = None for msg in reversed(user_messages): content = msg.content.strip() content_lower = content.lower() + + # Skip messages that are clearly JSON-encoded assistant responses or errors + # But be less aggressive - only skip if it's clearly not a user query + if content.startswith("[{") or content.startswith("[{"): + # Likely JSON-encoded assistant response + continue if any( pattern in content_lower for pattern in [ - "<", - ">", - "assistant:", + '"role": "assistant"', + '"role":"assistant"', "error:", - "i apologize", - "i'm having trouble", ] ): continue + # Don't skip messages that just happen to contain these words naturally user_content = content break + # Fallback to original user message if no valid recent message found + if not user_content and original_user_message: + # Check if original message is valid (not JSON-encoded) + if not ( + original_user_message.startswith("[{") + or original_user_message.startswith("[{") + ): + user_content = original_user_message + logger.info(f"Using original user message: {user_content[:200]}") + if not user_content: logger.warning("No valid user message found") return { @@ -244,6 +279,39 @@ async def extract_flight_info_from_messages(messages): "destination_airport_code": flight_info.get("destination_airport_code"), } + # Fallback: If destination is missing but we have origin, try to infer from conversation context + if result["origin"] and not result["destination"]: + # Look for cities mentioned in previous messages + for msg in reversed(conversation_context): + if msg["role"] == "user": + content = msg["content"] + # Look for weather queries mentioning cities + if ( + "weather" in content.lower() + or "forecast" in content.lower() + ): + # Common patterns: "weather in [city]", "forecast for [city]", "weather [city]" + patterns = [ + r"(?:weather|forecast).*?(?:in|for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + r"weather\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + ] + for pattern in patterns: + city_match = re.search(pattern, content, re.IGNORECASE) + if city_match: + potential_city = city_match.group(1).strip() + # Don't use the same city as origin + if ( + potential_city.lower() + != result["origin"].lower() + ): + logger.info( + f"Inferring destination from conversation context: {potential_city}" + ) + result["destination"] = potential_city + break + if result["destination"]: + break + logger.info(f"LLM extracted flight info: {result}") return result @@ -472,6 +540,34 @@ async def prepare_flight_messages(request_body: ChatCompletionRequest): origin_code = flight_info.get("origin_airport_code") dest_code = flight_info.get("destination_airport_code") + # Enhanced context extraction: If destination is missing, try to infer from conversation + if origin and not destination: + # Look through conversation history for mentioned cities + mentioned_cities = set() + for msg in request_body.messages: + if msg.role == "user": + content = msg.content + # Extract cities from weather queries: "weather in [city]", "forecast for [city]" + weather_patterns = [ + r"(?:weather|forecast).*?(?:in|for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + r"weather\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + ] + for pattern in weather_patterns: + matches = re.findall(pattern, content, re.IGNORECASE) + for match in matches: + city = match.strip() + # Don't use same city as origin, and validate it's a real city name + if city.lower() != origin.lower() and len(city.split()) <= 3: + mentioned_cities.add(city) + + # If we found cities in context, use the first one as destination + if mentioned_cities: + destination = list(mentioned_cities)[0] + logger.info( + f"Inferred destination from conversation context: {destination}" + ) + flight_info["destination"] = destination + if origin and not origin_code: origin_code = await resolve_airport_code(origin) if destination and not dest_code: diff --git a/demos/use_cases/travel_booking/src/travel_agents/weather_agent.py b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/weather_agent.py similarity index 71% rename from demos/use_cases/travel_booking/src/travel_agents/weather_agent.py rename to demos/use_cases/agent_orchestration[travel]/src/travel_agents/weather_agent.py index 3826cee8..11ed3469 100644 --- a/demos/use_cases/travel_booking/src/travel_agents/weather_agent.py +++ b/demos/use_cases/agent_orchestration[travel]/src/travel_agents/weather_agent.py @@ -1,4 +1,5 @@ import json +import re from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse from openai import AsyncOpenAI @@ -50,34 +51,42 @@ CRITICAL INSTRUCTIONS: - Always provide temperatures in both Celsius and Fahrenheit when available - If temperature is null, say "temperature data unavailable" rather than making up numbers -3. ERROR HANDLING: +3. MULTI-PART QUERIES AND MULTI-AGENT COLLABORATION: + - If the user asks multiple questions in one message (e.g., "What's the weather in Seattle, and what flights go to New York?"), focus ONLY on answering the weather-related part + - When queries contain multiple intents (weather + flights, weather + currency), you are part of a coordinated response where each agent handles their domain + - Provide complete weather information directly without mentioning other agents or deferring to them + - Example: "Here's the weather in Seattle: [provide complete weather info]." (Do NOT say "other agents may handle flights") + - Do NOT attempt to answer questions outside your weather expertise + - Simply provide your weather response - the system coordinates responses from multiple agents automatically + +4. ERROR HANDLING: - If the forecast array contains an "error" field, acknowledge the issue politely - If temperature or condition is null/None, mention that specific data is unavailable - Never invent or guess weather data - only use what's provided - If location couldn't be determined, acknowledge this but still provide available data -4. RESPONSE FORMAT: +5. RESPONSE FORMAT: - For single-day queries: Provide current conditions, temperature, and condition - For multi-day forecasts: List each day with date, day name, high/low temps, and condition - Include sunrise/sunset times when available and relevant - Use natural, conversational language - Be concise but complete -5. CONDITION DESCRIPTIONS: +6. CONDITION DESCRIPTIONS: - Use the exact condition provided (e.g., "Clear sky", "Rainy", "Partly Cloudy") - Add context when helpful (e.g., "perfect for outdoor activities" for clear skies) -6. LOCATION HANDLING: +7. LOCATION HANDLING: - Always mention the location name from the data - If the location differs from what the user asked, acknowledge this politely -7. RESPONSE STYLE: +8. RESPONSE STYLE: - Be friendly and professional - Use natural language, not technical jargon - Format dates and times clearly - For forecasts, use bullet points or numbered lists for clarity -Remember: Only use the data provided. Never fabricate weather information. If data is missing, clearly state what's unavailable.""" +Remember: Only use the data provided. Never fabricate weather information. If data is missing, clearly state what's unavailable. Focus ONLY on weather-related questions. Provide complete weather responses without mentioning other agents.""" async def geocode_city(city: str) -> Optional[dict]: @@ -264,68 +273,114 @@ async def get_weather_data(location: str, days: int = 1): return {"location": location_name, "forecast": forecast} -LOCATION_EXTRACTION_PROMPT = """You are a location extraction assistant. Your ONLY job is to extract the geographic location (city, state, country, etc.) from user messages. +LOCATION_EXTRACTION_PROMPT = """You are a location extraction assistant for WEATHER queries. Your ONLY job is to extract the geographic location (city, state, country, etc.) that the user is asking about for WEATHER information. CRITICAL RULES: -1. Extract ONLY the location name - nothing else +1. Extract ONLY the location name associated with WEATHER questions - nothing else 2. Return just the location name in plain text (e.g., "London", "New York", "Paris, France") -3. If the user mentions multiple locations, extract the PRIMARY location they're asking about -4. Ignore error messages, HTML tags, and assistant responses -5. If no clear location is found, return exactly: "NOT_FOUND" -6. Clean the location name - remove words like "about", "for", "in", "the weather in", etc. -7. Return the location in a format suitable for geocoding (city name, or "City, State", or "City, Country") +3. If the user mentions multiple locations in a multi-part query, extract ONLY the location mentioned in the WEATHER part + - Example: "What's the weather in Seattle, and what is one flight that goes direct to Atlanta?" → Extract "Seattle" (the weather location, NOT Atlanta which is for flights) + - Example: "Weather in London and flights to Paris" → Extract "London" (weather location) +4. Look for patterns like "weather in [location]", "forecast for [location]", "weather [location]" +5. Ignore error messages, HTML tags, and assistant responses +6. If no clear weather-related location is found, return exactly: "NOT_FOUND" +7. Clean the location name - remove words like "about", "for", "in", "the weather in", etc. +8. Return the location in a format suitable for geocoding (city name, or "City, State", or "City, Country") Examples: - "What's the weather in London?" → "London" - "Tell me about the weather for New York" → "New York" - "Weather forecast for Paris, France" → "Paris, France" -- "I'm going to Seattle" → "Seattle" -- "About London" → "London" +- "What's the weather in Seattle, and what is one flight that goes direct to Atlanta?" → "Seattle" (NOT Atlanta - that's for flights) +- "Weather in Istanbul and flights to Seattle" → "Istanbul" (weather location) +- "I'm going to Seattle" → "Seattle" (if context suggests weather query) - "What's happening?" → "NOT_FOUND" -Now extract the location from this message:""" +Now extract the WEATHER location from this message:""" async def extract_location_from_messages(messages): - """Extract location from user messages using LLM.""" + """Extract location from user messages using LLM, focusing on weather-related locations.""" user_messages = [msg for msg in messages if msg.role == "user"] if not user_messages: logger.warning("No user messages found, using default: New York") return "New York" + # CRITICAL: Always preserve the FIRST user message (original query) for multi-agent scenarios + # When Plano processes multiple agents, it may add assistant responses that get filtered out, + # but we need to always use the original user query + original_user_message = user_messages[0].content.strip() if user_messages else None + + # Try to find a valid recent user message first (for follow-up queries) user_content = None for msg in reversed(user_messages): content = msg.content.strip() content_lower = content.lower() + + # Skip messages that are clearly JSON-encoded assistant responses or errors + # But be less aggressive - only skip if it's clearly not a user query + if content.startswith("[{") or content.startswith("[{"): + # Likely JSON-encoded assistant response + continue if any( pattern in content_lower for pattern in [ - "<", - ">", - "assistant:", + '"role": "assistant"', + '"role":"assistant"', "error:", - "i apologize", - "i'm having trouble", ] ): continue + # Don't skip messages that just happen to contain these words naturally user_content = content break + # Fallback to original user message if no valid recent message found + if not user_content and original_user_message: + # Check if original message is valid (not JSON-encoded) + if not ( + original_user_message.startswith("[{") + or original_user_message.startswith("[{") + ): + user_content = original_user_message + logger.info(f"Using original user message: {user_content[:200]}") + if not user_content: logger.warning("No valid user message found, using default: New York") return "New York" try: - logger.info(f"Extracting location from user message: {user_content[:200]}") + logger.info( + f"Extracting weather location from user message: {user_content[:200]}" + ) + + # Build context from conversation history + conversation_context = [] + for msg in messages: + content = msg.content.strip() + content_lower = content.lower() + if any( + pattern in content_lower + for pattern in ["<", ">", "error:", "i apologize", "i'm having trouble"] + ): + continue + conversation_context.append({"role": msg.role, "content": content}) + + # Use last 5 messages for context + context_messages = ( + conversation_context[-5:] + if len(conversation_context) > 5 + else conversation_context + ) + + llm_messages = [{"role": "system", "content": LOCATION_EXTRACTION_PROMPT}] + for msg in context_messages: + llm_messages.append({"role": msg["role"], "content": msg["content"]}) response = await archgw_client.chat.completions.create( model=LOCATION_MODEL, - messages=[ - {"role": "system", "content": LOCATION_EXTRACTION_PROMPT}, - {"role": "user", "content": user_content}, - ], + messages=llm_messages, temperature=0.1, max_tokens=50, ) @@ -334,18 +389,55 @@ async def extract_location_from_messages(messages): location = location.strip("\"'`.,!?") if not location or location.upper() == "NOT_FOUND": + # Fallback: Try regex extraction for weather patterns + weather_patterns = [ + r"weather\s+(?:in|for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + r"forecast\s+(?:in|for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + r"weather\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + ] + for msg in reversed(context_messages): + if msg["role"] == "user": + content = msg["content"] + for pattern in weather_patterns: + match = re.search(pattern, content, re.IGNORECASE) + if match: + potential_location = match.group(1).strip() + logger.info( + f"Fallback regex extracted weather location: {potential_location}" + ) + return potential_location + logger.warning( f"LLM could not extract location from message, using default: New York" ) return "New York" - logger.info(f"LLM extracted location: {location}") + logger.info(f"LLM extracted weather location: {location}") return location except Exception as e: - logger.error( - f"Error extracting location with LLM: {e}, using default: New York" - ) + logger.error(f"Error extracting location with LLM: {e}, trying fallback regex") + # Fallback regex extraction + try: + for msg in reversed(messages): + if msg.role == "user": + content = msg.content + weather_patterns = [ + r"weather\s+(?:in|for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + r"forecast\s+(?:in|for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)", + ] + for pattern in weather_patterns: + match = re.search(pattern, content, re.IGNORECASE) + if match: + potential_location = match.group(1).strip() + logger.info( + f"Fallback regex extracted weather location: {potential_location}" + ) + return potential_location + except: + pass + + logger.error("All extraction methods failed, using default: New York") return "New York" diff --git a/demos/use_cases/travel_booking/start_agents.sh b/demos/use_cases/agent_orchestration[travel]/start_agents.sh similarity index 100% rename from demos/use_cases/travel_booking/start_agents.sh rename to demos/use_cases/agent_orchestration[travel]/start_agents.sh diff --git a/demos/use_cases/travel_booking/uv.lock b/demos/use_cases/agent_orchestration[travel]/uv.lock similarity index 100% rename from demos/use_cases/travel_booking/uv.lock rename to demos/use_cases/agent_orchestration[travel]/uv.lock