Update app.py
Browse files
app.py
CHANGED
|
@@ -6,7 +6,6 @@ import gradio as gr
|
|
| 6 |
import openai
|
| 7 |
|
| 8 |
# -------------------- CONFIGURATION --------------------
|
| 9 |
-
# Use environment variables for security (set them before running)
|
| 10 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "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")
|
| 11 |
OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "") # Optional, for live weather
|
| 12 |
|
|
@@ -50,8 +49,7 @@ def get_weather(city: str) -> dict:
|
|
| 50 |
data = resp.json()
|
| 51 |
if resp.status_code != 200:
|
| 52 |
return {"error": f"Weather API error: {data.get('message', 'unknown')}"}
|
| 53 |
-
|
| 54 |
-
weather_info = {
|
| 55 |
"city": city,
|
| 56 |
"temperature": data["main"]["temp"],
|
| 57 |
"condition": data["weather"][0]["description"],
|
|
@@ -59,7 +57,6 @@ def get_weather(city: str) -> dict:
|
|
| 59 |
"wind_speed": data["wind"]["speed"],
|
| 60 |
"precipitation": data.get("rain", {}).get("1h", 0)
|
| 61 |
}
|
| 62 |
-
return weather_info
|
| 63 |
except Exception as e:
|
| 64 |
return {"error": f"Weather service unavailable: {str(e)}"}
|
| 65 |
|
|
@@ -96,52 +93,6 @@ def get_attractions(city: str) -> dict:
|
|
| 96 |
else:
|
| 97 |
return {"error": f"No attractions data available for {city}.", "city": city}
|
| 98 |
|
| 99 |
-
def compose_itinerary(city: str, weather: dict, attractions: list, budget: dict = None) -> str:
|
| 100 |
-
"""
|
| 101 |
-
Generate a day trip itinerary using the tool outputs.
|
| 102 |
-
Calls OpenAI to format the plan based on verified data.
|
| 103 |
-
"""
|
| 104 |
-
# Build context string
|
| 105 |
-
weather_text = f"{weather['condition'].capitalize()}, {weather['temperature']}°C"
|
| 106 |
-
if weather.get('precipitation', 0) > 0:
|
| 107 |
-
weather_text += f", chance of rain {weather['precipitation']}mm"
|
| 108 |
-
if "note" in weather:
|
| 109 |
-
weather_text += f" ({weather['note']})"
|
| 110 |
-
|
| 111 |
-
attractions_text = "\n".join([
|
| 112 |
-
f"- {a['name']} ({a['type']}, ~{a['duration_hours']} hrs, entry: {a['entry_fee']} {a.get('currency', '')})"
|
| 113 |
-
for a in attractions
|
| 114 |
-
])
|
| 115 |
-
|
| 116 |
-
budget_text = ""
|
| 117 |
-
if budget and "error" not in budget:
|
| 118 |
-
budget_text = f"Budget: {budget['amount']} {budget['from']} (approx. {budget['converted']} {budget['to']} in local currency)"
|
| 119 |
-
|
| 120 |
-
prompt = f"""
|
| 121 |
-
You are a travel assistant. Create a one-day itinerary for {city} based on the following verified information.
|
| 122 |
-
|
| 123 |
-
Weather: {weather_text}
|
| 124 |
-
Attractions available:
|
| 125 |
-
{attractions_text}
|
| 126 |
-
{budget_text}
|
| 127 |
-
|
| 128 |
-
The itinerary should:
|
| 129 |
-
- Be realistic given the weather (e.g., outdoor activities if sunny, indoor if rain).
|
| 130 |
-
- Sequence attractions logically (considering location and opening hours if known).
|
| 131 |
-
- Include estimated times for each activity.
|
| 132 |
-
- Suggest meal times and local food options (use common knowledge, do not invent specific restaurants).
|
| 133 |
-
- If a budget is provided, mention if attractions have entry fees and stay within budget.
|
| 134 |
-
- Use markdown for readability (headings, bullet points, emojis).
|
| 135 |
-
|
| 136 |
-
Return only the itinerary.
|
| 137 |
-
"""
|
| 138 |
-
response = client.chat.completions.create(
|
| 139 |
-
model="gpt-3.5-turbo",
|
| 140 |
-
messages=[{"role": "user", "content": prompt}],
|
| 141 |
-
temperature=0.7
|
| 142 |
-
)
|
| 143 |
-
return response.choices[0].message.content
|
| 144 |
-
|
| 145 |
# -------------------- TOOL SCHEMAS FOR OPENAI --------------------
|
| 146 |
tools = [
|
| 147 |
{
|
|
@@ -188,63 +139,73 @@ tools = [
|
|
| 188 |
}
|
| 189 |
}
|
| 190 |
}
|
| 191 |
-
# Note: compose_itinerary is not exposed as a tool; it's called by the agent after gathering data.
|
| 192 |
]
|
| 193 |
|
| 194 |
# -------------------- AGENT ORCHESTRATION --------------------
|
| 195 |
def run_agent(user_message, history):
|
| 196 |
"""
|
| 197 |
-
Main agent loop: maintains conversation, calls tools, and
|
| 198 |
"""
|
| 199 |
-
# System prompt
|
| 200 |
system_prompt = """You are a helpful travel planning assistant. You have access to tools that can:
|
| 201 |
- Get current weather for a city
|
| 202 |
- Convert currency
|
| 203 |
- Retrieve tourist attractions for a city
|
| 204 |
|
| 205 |
-
Your goal is to help the user plan a day trip.
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
|
|
|
|
|
|
| 209 |
|
|
|
|
| 210 |
messages = [{"role": "system", "content": system_prompt}]
|
| 211 |
-
# Add conversation history (Gradio passes history as list of [user, assistant] pairs)
|
| 212 |
for user_msg, asst_msg in history:
|
| 213 |
messages.append({"role": "user", "content": user_msg})
|
| 214 |
if asst_msg:
|
| 215 |
messages.append({"role": "assistant", "content": asst_msg})
|
| 216 |
messages.append({"role": "user", "content": user_message})
|
| 217 |
|
| 218 |
-
#
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
-
# Max iterations to prevent infinite loops
|
| 222 |
-
for _ in range(5):
|
| 223 |
-
response = client.chat.completions.create(
|
| 224 |
-
model="gpt-3.5-turbo",
|
| 225 |
-
messages=messages,
|
| 226 |
-
tools=tools,
|
| 227 |
-
tool_choice="auto"
|
| 228 |
-
)
|
| 229 |
msg = response.choices[0].message
|
| 230 |
|
| 231 |
# If the model wants to call tools
|
| 232 |
if msg.tool_calls:
|
|
|
|
| 233 |
# Add assistant message with tool calls to history
|
| 234 |
messages.append(msg)
|
| 235 |
for tool_call in msg.tool_calls:
|
| 236 |
func_name = tool_call.function.name
|
| 237 |
args = json.loads(tool_call.function.arguments)
|
|
|
|
|
|
|
| 238 |
# Execute the tool
|
| 239 |
if func_name == "get_weather":
|
| 240 |
result = get_weather(args["city"])
|
| 241 |
-
tool_outputs["weather"] = result
|
| 242 |
elif func_name == "convert_currency":
|
| 243 |
result = convert_currency(args["amount"], args["from_currency"], args["to_currency"])
|
| 244 |
-
tool_outputs["budget"] = result
|
| 245 |
elif func_name == "get_attractions":
|
| 246 |
result = get_attractions(args["city"])
|
| 247 |
-
tool_outputs["attractions"] = result
|
| 248 |
else:
|
| 249 |
result = {"error": "Unknown tool"}
|
| 250 |
|
|
@@ -254,27 +215,17 @@ Be friendly, concise, and helpful."""
|
|
| 254 |
"tool_call_id": tool_call.id,
|
| 255 |
"content": json.dumps(result)
|
| 256 |
})
|
| 257 |
-
|
|
|
|
| 258 |
continue
|
| 259 |
else:
|
| 260 |
-
# No tool calls:
|
| 261 |
final_text = msg.content
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
# To be safe, we can check if we have weather and attractions, and then call compose_itinerary.
|
| 265 |
-
if "weather" in tool_outputs and "attractions" in tool_outputs and "error" not in tool_outputs["attractions"]:
|
| 266 |
-
# We have the data, generate itinerary
|
| 267 |
-
city = tool_outputs["attractions"]["city"]
|
| 268 |
-
weather = tool_outputs["weather"]
|
| 269 |
-
attractions = tool_outputs["attractions"]["attractions"]
|
| 270 |
-
budget = tool_outputs.get("budget")
|
| 271 |
-
itinerary = compose_itinerary(city, weather, attractions, budget)
|
| 272 |
-
return itinerary
|
| 273 |
-
else:
|
| 274 |
-
# Model's response is a clarification or intermediate answer
|
| 275 |
-
return final_text
|
| 276 |
|
| 277 |
-
|
|
|
|
| 278 |
|
| 279 |
# -------------------- GRADIO INTERFACE --------------------
|
| 280 |
def chat_interface(message, history):
|
|
@@ -293,7 +244,6 @@ with gr.Blocks(title="TouristGuide AI Agent") as demo:
|
|
| 293 |
Your personal travel planner powered by AI and real-time tools.
|
| 294 |
Ask for a day trip itinerary in any supported city (Kigali, Kampala, Nairobi, etc.) with optional budget and currency conversion.
|
| 295 |
""")
|
| 296 |
-
# Removed bubble_full_width (no longer supported)
|
| 297 |
chatbot = gr.Chatbot(label="Conversation", height=500)
|
| 298 |
msg = gr.Textbox(label="Your message", placeholder="e.g., Plan a day trip in Kigali with a budget of 150 USD", lines=2)
|
| 299 |
clear = gr.Button("Clear conversation")
|
|
@@ -307,5 +257,4 @@ with gr.Blocks(title="TouristGuide AI Agent") as demo:
|
|
| 307 |
clear.click(lambda: None, None, chatbot, queue=False)
|
| 308 |
|
| 309 |
if __name__ == "__main__":
|
| 310 |
-
# Pass css to launch() as per Gradio 6.0+
|
| 311 |
demo.launch(share=False, server_name="0.0.0.0", css=css)
|
|
|
|
| 6 |
import openai
|
| 7 |
|
| 8 |
# -------------------- CONFIGURATION --------------------
|
|
|
|
| 9 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "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")
|
| 10 |
OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "") # Optional, for live weather
|
| 11 |
|
|
|
|
| 49 |
data = resp.json()
|
| 50 |
if resp.status_code != 200:
|
| 51 |
return {"error": f"Weather API error: {data.get('message', 'unknown')}"}
|
| 52 |
+
return {
|
|
|
|
| 53 |
"city": city,
|
| 54 |
"temperature": data["main"]["temp"],
|
| 55 |
"condition": data["weather"][0]["description"],
|
|
|
|
| 57 |
"wind_speed": data["wind"]["speed"],
|
| 58 |
"precipitation": data.get("rain", {}).get("1h", 0)
|
| 59 |
}
|
|
|
|
| 60 |
except Exception as e:
|
| 61 |
return {"error": f"Weather service unavailable: {str(e)}"}
|
| 62 |
|
|
|
|
| 93 |
else:
|
| 94 |
return {"error": f"No attractions data available for {city}.", "city": city}
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
# -------------------- TOOL SCHEMAS FOR OPENAI --------------------
|
| 97 |
tools = [
|
| 98 |
{
|
|
|
|
| 139 |
}
|
| 140 |
}
|
| 141 |
}
|
|
|
|
| 142 |
]
|
| 143 |
|
| 144 |
# -------------------- AGENT ORCHESTRATION --------------------
|
| 145 |
def run_agent(user_message, history):
|
| 146 |
"""
|
| 147 |
+
Main agent loop: maintains conversation, calls tools, and returns final answer.
|
| 148 |
"""
|
| 149 |
+
# System prompt with clear instructions
|
| 150 |
system_prompt = """You are a helpful travel planning assistant. You have access to tools that can:
|
| 151 |
- Get current weather for a city
|
| 152 |
- Convert currency
|
| 153 |
- Retrieve tourist attractions for a city
|
| 154 |
|
| 155 |
+
Your goal is to help the user plan a day trip. Follow these steps:
|
| 156 |
+
1. If the user's request is missing essential information (city, budget, currency, etc.), ask clarifying questions.
|
| 157 |
+
2. Once you have all necessary details, call the appropriate tools to fetch real-time data.
|
| 158 |
+
3. After receiving tool results, use that information to compose a detailed, day-long itinerary.
|
| 159 |
+
4. Do not invent any data; only use the tool outputs.
|
| 160 |
+
5. Be friendly, concise, and helpful. Use markdown for readability (headings, bullet points, emojis)."""
|
| 161 |
|
| 162 |
+
# Build message list from history
|
| 163 |
messages = [{"role": "system", "content": system_prompt}]
|
|
|
|
| 164 |
for user_msg, asst_msg in history:
|
| 165 |
messages.append({"role": "user", "content": user_msg})
|
| 166 |
if asst_msg:
|
| 167 |
messages.append({"role": "assistant", "content": asst_msg})
|
| 168 |
messages.append({"role": "user", "content": user_message})
|
| 169 |
|
| 170 |
+
# Debug: print the conversation so far
|
| 171 |
+
print("\n" + "="*50)
|
| 172 |
+
print("USER MESSAGE:", user_message)
|
| 173 |
+
print("="*50)
|
| 174 |
+
|
| 175 |
+
# Iterate up to 5 turns (to prevent infinite loops)
|
| 176 |
+
for turn in range(5):
|
| 177 |
+
print(f"\n--- Turn {turn+1} ---")
|
| 178 |
+
try:
|
| 179 |
+
response = client.chat.completions.create(
|
| 180 |
+
model="gpt-3.5-turbo", # You can change to "gpt-4" if available
|
| 181 |
+
messages=messages,
|
| 182 |
+
tools=tools,
|
| 183 |
+
tool_choice="auto"
|
| 184 |
+
)
|
| 185 |
+
except Exception as e:
|
| 186 |
+
error_msg = f"OpenAI API error: {str(e)}"
|
| 187 |
+
print(error_msg)
|
| 188 |
+
return error_msg
|
| 189 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
msg = response.choices[0].message
|
| 191 |
|
| 192 |
# If the model wants to call tools
|
| 193 |
if msg.tool_calls:
|
| 194 |
+
print(f"Model requested {len(msg.tool_calls)} tool call(s).")
|
| 195 |
# Add assistant message with tool calls to history
|
| 196 |
messages.append(msg)
|
| 197 |
for tool_call in msg.tool_calls:
|
| 198 |
func_name = tool_call.function.name
|
| 199 |
args = json.loads(tool_call.function.arguments)
|
| 200 |
+
print(f" Calling {func_name} with args: {args}")
|
| 201 |
+
|
| 202 |
# Execute the tool
|
| 203 |
if func_name == "get_weather":
|
| 204 |
result = get_weather(args["city"])
|
|
|
|
| 205 |
elif func_name == "convert_currency":
|
| 206 |
result = convert_currency(args["amount"], args["from_currency"], args["to_currency"])
|
|
|
|
| 207 |
elif func_name == "get_attractions":
|
| 208 |
result = get_attractions(args["city"])
|
|
|
|
| 209 |
else:
|
| 210 |
result = {"error": "Unknown tool"}
|
| 211 |
|
|
|
|
| 215 |
"tool_call_id": tool_call.id,
|
| 216 |
"content": json.dumps(result)
|
| 217 |
})
|
| 218 |
+
print(f" Result: {result}")
|
| 219 |
+
# Continue the loop so the model can see the tool results
|
| 220 |
continue
|
| 221 |
else:
|
| 222 |
+
# No tool calls: this is the final answer (or a clarification)
|
| 223 |
final_text = msg.content
|
| 224 |
+
print(f"Final answer: {final_text[:100]}...") # print first 100 chars
|
| 225 |
+
return final_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
+
# If we exit the loop without returning, something went wrong
|
| 228 |
+
return "I'm having trouble processing your request. Please try again or rephrase."
|
| 229 |
|
| 230 |
# -------------------- GRADIO INTERFACE --------------------
|
| 231 |
def chat_interface(message, history):
|
|
|
|
| 244 |
Your personal travel planner powered by AI and real-time tools.
|
| 245 |
Ask for a day trip itinerary in any supported city (Kigali, Kampala, Nairobi, etc.) with optional budget and currency conversion.
|
| 246 |
""")
|
|
|
|
| 247 |
chatbot = gr.Chatbot(label="Conversation", height=500)
|
| 248 |
msg = gr.Textbox(label="Your message", placeholder="e.g., Plan a day trip in Kigali with a budget of 150 USD", lines=2)
|
| 249 |
clear = gr.Button("Clear conversation")
|
|
|
|
| 257 |
clear.click(lambda: None, None, chatbot, queue=False)
|
| 258 |
|
| 259 |
if __name__ == "__main__":
|
|
|
|
| 260 |
demo.launch(share=False, server_name="0.0.0.0", css=css)
|