MathWizard1729 commited on
Commit
b14190d
Β·
verified Β·
1 Parent(s): 07ca3ed

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -153
app.py CHANGED
@@ -1,196 +1,175 @@
1
- import os
2
- import json
3
- import requests
4
- import boto3
5
- import streamlit as st
6
  from dotenv import load_dotenv
7
 
8
- # Load env (for local dev / Hugging Face secrets)
 
 
9
  load_dotenv()
10
-
11
- # Configs
12
  OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
13
- AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
14
-
15
- # AWS Bedrock Runtime
16
- session = boto3.Session(
17
- aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
18
- aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
19
- region_name=AWS_REGION
20
- )
21
- bedrock_runtime = session.client("bedrock-runtime")
22
-
23
- # Streamlit Page Config
24
- st.set_page_config(page_title="🌀️ Weather Umbrella Advisor", page_icon="β˜”", layout="centered")
25
-
26
- # --- Title Section ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  st.markdown("""
28
- <div style="text-align: center;">
29
- <h1 style="color: #3c79f5;">β˜” Weather Umbrella Advisor</h1>
30
- <p style="font-size: 18px;">Ask me if you need to carry an umbrella tomorrow, powered by <b>Claude + OpenWeatherMap</b>.</p>
31
  </div>
32
  """, unsafe_allow_html=True)
33
 
34
- # Chat history state
 
 
35
  if "messages" not in st.session_state:
36
  st.session_state.messages = []
37
 
38
- # --- Display Past Messages ---
39
- for msg in st.session_state.messages:
40
- with st.chat_message(msg["role"]):
41
- st.markdown(msg["content"])
42
-
43
- # --- Weather API Call ---
44
- def get_weather(location):
45
- """Fetches weather data for next 24 hours for given city."""
46
- if not location.strip():
47
- return {"error": "Please specify a valid location."}
48
-
 
49
  try:
50
- geo_url = f"http://api.openweathermap.org/geo/1.0/direct?q={location}&limit=1&appid={OPENWEATHERMAP_API_KEY}"
51
- geo_resp = requests.get(geo_url).json()
52
- if not geo_resp:
53
- return {"error": f"Location '{location}' not found."}
54
-
55
- lat, lon = geo_resp[0]['lat'], geo_resp[0]['lon']
56
- weather_url = f"http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={OPENWEATHERMAP_API_KEY}&units=metric"
57
- weather_data = requests.get(weather_url).json()
58
- if 'list' not in weather_data:
59
- return {"error": f"Unable to fetch weather forecast for '{location}'."}
60
-
61
- forecast = [{
62
  "time": f["dt_txt"],
63
  "description": f["weather"][0]["description"].capitalize(),
64
- "rain_probability": round(f.get("pop", 0) * 100, 1),
65
- "temp": f["main"]["temp"],
66
- "humidity": f["main"]["humidity"]
67
- } for f in weather_data['list'][:8]] # 24 hrs = 8 x 3hr blocks
68
-
69
- return {"location": location.title(), "forecast": forecast}
70
-
71
  except Exception as e:
72
  return {"error": str(e)}
73
 
74
- # --- ReAct-Powered Response Generator ---
75
- def generate_react_response(user_input, conversation_history=""):
76
- """Uses Claude with ReAct to give umbrella recommendation."""
77
- system_prompt = """You are a helpful assistant using the ReAct (Reasoning + Acting) method to answer whether the user should carry an umbrella tomorrow.
78
 
79
- Steps:
80
  1. Think about the question.
81
- 2. Act using get_weather(location).
82
- 3. Observe the weather data.
83
- 4. Reason and give a clear answer.
84
 
85
- When you need weather data, reply in this format:
86
- {
87
- "thought": "Need weather info for [location]",
88
- "action": "get_weather",
89
- "action_input": {"location": "city_name"}
90
- }
91
 
92
- If no location is mentioned, ask the user to specify one.
93
-
94
- If you have the weather data, give a natural reply like:
95
- "You do not need an umbrella tomorrow in London, as it's expected to be sunny and dry."
96
- """
97
-
98
- messages = [{"role": "user", "content": f"{system_prompt}\n\nChat history:\n{conversation_history}\n\nUser: {user_input}"}]
99
-
100
- claude_body = {
101
  "anthropic_version": "bedrock-2023-05-31",
102
  "max_tokens": 1000,
103
  "temperature": 0.7,
104
  "top_p": 0.9,
105
- "messages": messages
 
 
 
106
  }
107
-
108
- response = bedrock_runtime.invoke_model(
109
  modelId="anthropic.claude-3-sonnet-20240229-v1:0",
110
  contentType="application/json",
111
  accept="application/json",
112
- body=json.dumps(claude_body),
113
  )
114
- content = json.loads(response["body"].read())["content"][0]["text"].strip()
115
 
116
- # Try parsing ReAct JSON
117
  try:
118
- parsed = json.loads(content)
119
- if parsed.get("action") == "get_weather":
120
- location = parsed["action_input"]["location"]
121
- if not location:
122
- return "Please tell me which city you're asking about 🌍."
123
-
124
- weather_data = get_weather(location)
125
- if "error" in weather_data:
126
- return weather_data["error"]
127
-
128
- forecast_str = json.dumps(weather_data, indent=2)
129
- reasoning_prompt = f"""Based on this weather forecast for {location}, give an umbrella recommendation:
130
-
131
- {forecast_str}
132
-
133
- Return a natural response that includes:
134
- - Location
135
- - Conditions (rain %, sky, etc.)
136
- - Clear YES/NO umbrella advice
137
- """
138
- final_response = bedrock_runtime.invoke_model(
139
  modelId="anthropic.claude-3-sonnet-20240229-v1:0",
140
  contentType="application/json",
141
  accept="application/json",
142
- body=json.dumps({
143
- "anthropic_version": "bedrock-2023-05-31",
144
- "max_tokens": 500,
145
- "temperature": 0.7,
146
- "messages": [{"role": "user", "content": reasoning_prompt}]
147
- })
148
  )
149
- return json.loads(final_response["body"].read())["content"][0]["text"].strip()
150
-
151
  except json.JSONDecodeError:
152
- pass # Possibly just a reply from Claude
153
-
154
- return content
155
 
156
- # --- Chat Input Handling ---
157
- def build_convo_history():
158
- return "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in st.session_state.messages[-4:]])
 
 
159
 
160
- if user_prompt := st.chat_input("Ask: Do I need an umbrella tomorrow?"):
161
- st.session_state.messages.append({"role": "user", "content": user_prompt})
162
- with st.chat_message("user"):
163
- st.markdown(user_prompt)
164
 
165
  with st.chat_message("assistant"):
166
- with st.spinner("Thinking... πŸ€”"):
167
- history = build_convo_history()
168
- response = generate_react_response(user_prompt, history)
169
- st.markdown(response)
170
-
171
- st.session_state.messages.append({"role": "assistant", "content": response})
172
-
173
- # --- Sidebar ---
174
  with st.sidebar:
175
  st.image("https://img.icons8.com/clouds/100/umbrella.png", width=100)
176
- st.markdown("## β˜€οΈ About")
177
- st.markdown("""
178
- **Weather Assistant** gives you umbrella advice using:
179
- - 🌦️ Real-time weather via **OpenWeatherMap**
180
- - 🧠 Smart reasoning via **Claude (Bedrock)**
181
- - πŸ€– ReAct method: Think β€’ Act β€’ Observe β€’ Reason
182
-
183
- ---
184
-
185
- ### πŸ’¬ Try Saying
186
- - "Should I bring an umbrella tomorrow?"
187
- - "Will it rain in Delhi tomorrow?"
188
- - "Do I need an umbrella in Tokyo?"
189
-
190
- ---
191
-
192
- ### πŸ”§ Tools Used
193
- - Claude 3 Sonnet (Bedrock)
194
- - OpenWeatherMap API
195
- - Streamlit (frontend)
196
- """)
 
1
+ ###############################################################################
2
+ # app.py – Weather Umbrella Advisor (Streamlit + OpenWeatherMap + Claude 3) #
3
+ ###############################################################################
4
+ import os, json, requests, boto3, streamlit as st
 
5
  from dotenv import load_dotenv
6
 
7
+ # --------------------------------------------------------------------------- #
8
+ # 1) Load env vars (local .env or HF Space β€œSecrets”) #
9
+ # --------------------------------------------------------------------------- #
10
  load_dotenv()
 
 
11
  OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
12
+ AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
13
+ AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID") # may be None
14
+ AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY") # may be None
15
+
16
+ # --- Heuristic: auto-swap if ID looks like a secret (contains β€œ/”) ----------
17
+ if AWS_ACCESS_KEY_ID and "/" in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY and "/" not in AWS_SECRET_ACCESS_KEY:
18
+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID
19
+
20
+ # --------------------------------------------------------------------------- #
21
+ # 2) Create Bedrock client, preferring explicit keys if they’re present #
22
+ # --------------------------------------------------------------------------- #
23
+ if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
24
+ session = boto3.Session(
25
+ aws_access_key_id = AWS_ACCESS_KEY_ID,
26
+ aws_secret_access_key = AWS_SECRET_ACCESS_KEY,
27
+ region_name = AWS_REGION,
28
+ )
29
+ else:
30
+ # Falls back to IAM role or default credentials chain
31
+ session = boto3.Session(region_name=AWS_REGION)
32
+
33
+ bedrock = session.client("bedrock-runtime")
34
+
35
+ # Quick sanity-check – will raise if creds are still wrong
36
+ try:
37
+ _ = bedrock.meta.region_name # simple attribute access forces no call
38
+ except Exception as e:
39
+ st.error(f"Credential problem: {e}")
40
+ st.stop()
41
+
42
+ # --------------------------------------------------------------------------- #
43
+ # 3) Streamlit page style #
44
+ # --------------------------------------------------------------------------- #
45
+ st.set_page_config(page_title="🌀️ Umbrella Advisor", page_icon="β˜”", layout="centered")
46
  st.markdown("""
47
+ <div style="text-align:center">
48
+ <h1 style="color:#3c79f5;">β˜” Weather Umbrella Advisor</h1>
49
+ <p style="font-size:18px">Ask if you need an umbrella tomorrow – powered by <b>Claude 3</b> &amp; <b>OpenWeatherMap</b>.</p>
50
  </div>
51
  """, unsafe_allow_html=True)
52
 
53
+ # --------------------------------------------------------------------------- #
54
+ # 4) Conversation state #
55
+ # --------------------------------------------------------------------------- #
56
  if "messages" not in st.session_state:
57
  st.session_state.messages = []
58
 
59
+ for m in st.session_state.messages:
60
+ with st.chat_message(m["role"]):
61
+ st.markdown(m["content"])
62
+
63
+ # --------------------------------------------------------------------------- #
64
+ # 5) Helper – get weather #
65
+ # --------------------------------------------------------------------------- #
66
+ def get_weather(city: str):
67
+ if not city.strip():
68
+ return {"error": "Please give me a city name."}
69
+
70
+ geo = f"http://api.openweathermap.org/geo/1.0/direct?q={city}&limit=1&appid={OPENWEATHERMAP_API_KEY}"
71
  try:
72
+ loc = requests.get(geo, timeout=10).json()
73
+ if not loc:
74
+ return {"error": f"City β€œ{city}” not found."}
75
+ lat, lon = loc[0]["lat"], loc[0]["lon"]
76
+ wurl = f"http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={OPENWEATHERMAP_API_KEY}&units=metric"
77
+ data = requests.get(wurl, timeout=10).json()
78
+ if "list" not in data:
79
+ return {"error": f"No forecast for β€œ{city}”."}
80
+ fc = [{
 
 
 
81
  "time": f["dt_txt"],
82
  "description": f["weather"][0]["description"].capitalize(),
83
+ "rain_probability": round(f.get("pop", 0)*100, 1),
84
+ "temp": f["main"]["temp"]
85
+ } for f in data["list"][:8]]
86
+ return {"location": city.title(), "forecast": fc}
 
 
 
87
  except Exception as e:
88
  return {"error": str(e)}
89
 
90
+ # --------------------------------------------------------------------------- #
91
+ # 6) Helper – talk to Claude (ReAct) #
92
+ # --------------------------------------------------------------------------- #
93
+ SYSTEM_PROMPT = """You are a helpful umbrella advisor using ReAct:
94
 
 
95
  1. Think about the question.
96
+ 2. If needed, act with get_weather(location).
97
+ 3. Observe results.
98
+ 4. Reason and answer.
99
 
100
+ When you need weather data, respond EXACTLY:
101
+ {"thought":"…","action":"get_weather","action_input":{"location":"City"}}"""
 
 
 
 
102
 
103
+ def ask_claude(user, history=""):
104
+ # First call
105
+ body = {
 
 
 
 
 
 
106
  "anthropic_version": "bedrock-2023-05-31",
107
  "max_tokens": 1000,
108
  "temperature": 0.7,
109
  "top_p": 0.9,
110
+ "messages": [{
111
+ "role": "user",
112
+ "content": f"{SYSTEM_PROMPT}\n\nHistory:\n{history}\n\nUser: {user}"
113
+ }]
114
  }
115
+ raw = bedrock.invoke_model(
 
116
  modelId="anthropic.claude-3-sonnet-20240229-v1:0",
117
  contentType="application/json",
118
  accept="application/json",
119
+ body=json.dumps(body)
120
  )
121
+ txt = json.loads(raw["body"].read())["content"][0]["text"].strip()
122
 
123
+ # Try to parse ReAct JSON
124
  try:
125
+ j = json.loads(txt)
126
+ if j.get("action") == "get_weather":
127
+ city = j["action_input"]["location"]
128
+ wx = get_weather(city)
129
+ if "error" in wx: return wx["error"]
130
+
131
+ # Second call – reasoning
132
+ reason_prompt = f"""Here is the forecast for {city}:
133
+
134
+ {json.dumps(wx, indent=2)}
135
+
136
+ Give a friendly answer: YES/NO umbrella, with reasoning."""
137
+ body2 = {
138
+ "anthropic_version": "bedrock-2023-05-31",
139
+ "max_tokens": 500,
140
+ "temperature": 0.7,
141
+ "messages":[{"role":"user","content":reason_prompt}]
142
+ }
143
+ raw2 = bedrock.invoke_model(
 
 
144
  modelId="anthropic.claude-3-sonnet-20240229-v1:0",
145
  contentType="application/json",
146
  accept="application/json",
147
+ body=json.dumps(body2)
 
 
 
 
 
148
  )
149
+ return json.loads(raw2["body"].read())["content"][0]["text"].strip()
 
150
  except json.JSONDecodeError:
151
+ pass
152
+ return txt
 
153
 
154
+ # --------------------------------------------------------------------------- #
155
+ # 7) Chat input #
156
+ # --------------------------------------------------------------------------- #
157
+ def last_history(n=4):
158
+ return "\n".join(f"{m['role'].capitalize()}: {m['content']}" for m in st.session_state.messages[-n:])
159
 
160
+ if prompt := st.chat_input("Ask: Do I need an umbrella tomorrow?"):
161
+ st.session_state.messages.append({"role":"user","content":prompt})
162
+ with st.chat_message("user"): st.markdown(prompt)
 
163
 
164
  with st.chat_message("assistant"):
165
+ with st.spinner("Thinking…"):
166
+ reply = ask_claude(prompt, last_history())
167
+ st.markdown(reply)
168
+ st.session_state.messages.append({"role":"assistant","content":reply})
169
+
170
+ # --------------------------------------------------------------------------- #
171
+ # 8) Sidebar #
172
+ # --------------------------------------------------------------------------- #
173
  with st.sidebar:
174
  st.image("https://img.icons8.com/clouds/100/umbrella.png", width=100)
175
+ st.markdown("### About\nUses **Claude 3 Sonnet (Bedrock)** + **OpenWeatherMap**")