Spaces:
Sleeping
Sleeping
Bhupen
commited on
Commit
·
6672eba
1
Parent(s):
95806b4
weather app using pydanticai
Browse files- app.py +525 -0
- requirements.txt +6 -0
app.py
ADDED
@@ -0,0 +1,525 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
st.markdown("""
|
4 |
+
**Weather agent**
|
5 |
+
|
6 |
+
Example of PydanticAI with `multiple tools` which the LLM needs to call in turn to answer a question.
|
7 |
+
""")
|
8 |
+
|
9 |
+
with st.expander("🎯 Objectives"):
|
10 |
+
st.markdown("""
|
11 |
+
- Use **OpenAI GPT-4o-mini** agent to `process natural language queries` about the weather.
|
12 |
+
- Fetch **geolocation** from a location string using the `Maps.co API`.
|
13 |
+
- Retrieve **real-time weather** using the Tomorrow.io API.
|
14 |
+
- Handle `retries`, `backoff`, and `logging` using **Logfire**.
|
15 |
+
- Integrate all parts in a clean, async-compatible **Streamlit UI**.
|
16 |
+
- Ensuring `concise` and `structured` responses.
|
17 |
+
""")
|
18 |
+
|
19 |
+
with st.expander("🧰 Pre-requisites"):
|
20 |
+
st.markdown("""
|
21 |
+
- Python 3.10+
|
22 |
+
- Streamlit
|
23 |
+
- AsyncClient (httpx)
|
24 |
+
- OpenAI `pydantic_ai` Agent
|
25 |
+
- Logfire for tracing/debugging
|
26 |
+
- Valid API Keys:
|
27 |
+
- [https://geocode.maps.co/](https://geocode.maps.co/)
|
28 |
+
- [https://www.tomorrow.io/](https://www.tomorrow.io/)
|
29 |
+
""")
|
30 |
+
|
31 |
+
st.code("""
|
32 |
+
pip install streamlit httpx logfire pydantic_ai
|
33 |
+
""")
|
34 |
+
|
35 |
+
with st.expander("⚙️ Step-by-Step Setup"):
|
36 |
+
st.markdown("**Imports and Global Client**")
|
37 |
+
st.code("""
|
38 |
+
import os
|
39 |
+
import asyncio
|
40 |
+
import streamlit as st
|
41 |
+
from dataclasses import dataclass
|
42 |
+
from typing import Any
|
43 |
+
import logfire
|
44 |
+
from httpx import AsyncClient
|
45 |
+
from pydantic_ai import Agent, RunContext, ModelRetry
|
46 |
+
|
47 |
+
logfire.configure(send_to_logfire='if-token-present')
|
48 |
+
client = AsyncClient()
|
49 |
+
""")
|
50 |
+
|
51 |
+
st.markdown("**Declare Dependencies**")
|
52 |
+
st.code("""
|
53 |
+
@dataclass
|
54 |
+
class Deps:
|
55 |
+
client: AsyncClient # client is an instance of AsyncClient (from httpx).
|
56 |
+
weather_api_key: str | None
|
57 |
+
geo_api_key: str | None
|
58 |
+
""")
|
59 |
+
|
60 |
+
st.markdown("**Setup Weather Agent**")
|
61 |
+
st.code("""
|
62 |
+
weather_agent = Agent(
|
63 |
+
'openai:gpt-4o-mini',
|
64 |
+
system_prompt=(
|
65 |
+
'Be concise, reply with one sentence. '
|
66 |
+
'Use the `get_lat_lng` tool to get the latitude and longitude of the locations, '
|
67 |
+
'then use the `get_weather` tool to get the weather.'
|
68 |
+
),
|
69 |
+
deps_type= Deps,
|
70 |
+
retries = 2,
|
71 |
+
)
|
72 |
+
""")
|
73 |
+
|
74 |
+
st.markdown("**Define Geocoding Tool with Retry**")
|
75 |
+
st.code("""
|
76 |
+
@weather_agent.tool
|
77 |
+
async def get_lat_lng(ctx: RunContext[Deps],
|
78 |
+
location_description: str,
|
79 |
+
max_retries: int = 5,
|
80 |
+
base_delay: int = 2) -> dict[str, float]:
|
81 |
+
"Get the latitude and longitude of a location with retry handling for rate limits."
|
82 |
+
|
83 |
+
if ctx.deps.geo_api_key is None:
|
84 |
+
return {'lat': 51.1, 'lng': -0.1} # Default to London
|
85 |
+
|
86 |
+
# Sets up API request parameters.
|
87 |
+
params = {'q': location_description, 'api_key': ctx.deps.geo_api_key}
|
88 |
+
|
89 |
+
# Loops for a maximum number of retries.
|
90 |
+
for attempt in range(max_retries):
|
91 |
+
try:
|
92 |
+
|
93 |
+
# Logs API call span with parameters.
|
94 |
+
with logfire.span('calling geocode API', params=params) as span:
|
95 |
+
|
96 |
+
# Sends async GET request.
|
97 |
+
r = await ctx.deps.client.get('https://geocode.maps.co/search', params=params)
|
98 |
+
|
99 |
+
# Checks if API rate limit is exceeded.
|
100 |
+
if r.status_code == 429:
|
101 |
+
|
102 |
+
# Exponential backoff
|
103 |
+
wait_time = base_delay * (2 ** attempt)
|
104 |
+
|
105 |
+
# Waits before retrying.
|
106 |
+
await asyncio.sleep(wait_time)
|
107 |
+
|
108 |
+
# Continues to the next retry attempt.
|
109 |
+
continue
|
110 |
+
|
111 |
+
r.raise_for_status()
|
112 |
+
data = r.json()
|
113 |
+
span.set_attribute('response', data)
|
114 |
+
|
115 |
+
if data:
|
116 |
+
# Extracts and returns latitude & longitude.
|
117 |
+
return {'lat': float(data[0]['lat']), 'lng': float(data[0]['lon'])}
|
118 |
+
else:
|
119 |
+
# Raises an error if no valid data is found.
|
120 |
+
raise ModelRetry('Could not find the location')
|
121 |
+
|
122 |
+
except Exception as e: # Catches HTTP errors.
|
123 |
+
print(f"Request failed: {e}") # Logs the failure.
|
124 |
+
|
125 |
+
raise ModelRetry('Failed after multiple retries')
|
126 |
+
|
127 |
+
""")
|
128 |
+
|
129 |
+
st.markdown("**Define Weather Tool**")
|
130 |
+
st.code("""
|
131 |
+
@weather_agent.tool
|
132 |
+
async def get_weather(ctx: RunContext[Deps], lat: float, lng: float) -> dict[str, Any]:
|
133 |
+
if ctx.deps.weather_api_key is None:
|
134 |
+
return {'temperature': '21 °C', 'description': 'Sunny'}
|
135 |
+
params = {'apikey': ctx.deps.weather_api_key, 'location': f'{lat},{lng}', 'units': 'metric'}
|
136 |
+
|
137 |
+
r = await ctx.deps.client.get('https://api.tomorrow.io/v4/weather/realtime', params=params)
|
138 |
+
r.raise_for_status()
|
139 |
+
|
140 |
+
data = r.json()
|
141 |
+
values = data['data']['values']
|
142 |
+
|
143 |
+
code_lookup = {
|
144 |
+
1000: 'Clear, Sunny', 1001: 'Cloudy', 1100: 'Mostly Clear', 1101: 'Partly Cloudy',
|
145 |
+
1102: 'Mostly Cloudy', 2000: 'Fog', 2100: 'Light Fog', 4000: 'Drizzle', 4001: 'Rain',
|
146 |
+
4200: 'Light Rain', 4201: 'Heavy Rain', 5000: 'Snow', 5001: 'Flurries',
|
147 |
+
5100: 'Light Snow', 5101: 'Heavy Snow', 6000: 'Freezing Drizzle', 6001: 'Freezing Rain',
|
148 |
+
6200: 'Light Freezing Rain', 6201: 'Heavy Freezing Rain', 7000: 'Ice Pellets',
|
149 |
+
7101: 'Heavy Ice Pellets', 7102: 'Light Ice Pellets', 8000: 'Thunderstorm',
|
150 |
+
}
|
151 |
+
|
152 |
+
return {
|
153 |
+
'temperature': f'{values["temperatureApparent"]:0.0f}°C',
|
154 |
+
'description': code_lookup.get(values['weatherCode'], 'Unknown'),
|
155 |
+
}
|
156 |
+
""")
|
157 |
+
|
158 |
+
st.markdown("**Wrapper to Run the Agent**")
|
159 |
+
st.code("""
|
160 |
+
async def run_weather_agent(user_input: str):
|
161 |
+
deps = Deps(
|
162 |
+
client=client,
|
163 |
+
weather_api_key = os.getenv("TOMORROW_IO_API_KEY"),
|
164 |
+
geo_api_key = os.getenv("GEOCODE_API_KEY")
|
165 |
+
)
|
166 |
+
result = await weather_agent.run(user_input, deps=deps)
|
167 |
+
return result.data
|
168 |
+
""")
|
169 |
+
|
170 |
+
st.markdown("**Streamlit UI with Async Handling**")
|
171 |
+
st.code("""
|
172 |
+
st.set_page_config(page_title="Weather Application", page_icon="🚀")
|
173 |
+
|
174 |
+
if "weather_response" not in st.session_state:
|
175 |
+
st.session_state.weather_response = None
|
176 |
+
|
177 |
+
st.title("Weather Agent App")
|
178 |
+
user_input = st.text_area("Enter a sentence with locations:", "What is the weather like in Bangalore, Chennai and Delhi?")
|
179 |
+
|
180 |
+
if st.button("Get Weather"):
|
181 |
+
with st.spinner("Fetching weather..."):
|
182 |
+
loop = asyncio.new_event_loop()
|
183 |
+
asyncio.set_event_loop(loop)
|
184 |
+
response = loop.run_until_complete(run_weather_agent(user_input))
|
185 |
+
st.session_state.weather_response = response
|
186 |
+
|
187 |
+
if st.session_state.weather_response:
|
188 |
+
st.info(st.session_state.weather_response)
|
189 |
+
""")
|
190 |
+
|
191 |
+
with st.expander("Description of Each Step"):
|
192 |
+
st.markdown("""
|
193 |
+
- **Imports**: Brings in all required packages including `httpx`, `logfire`, and `streamlit`.
|
194 |
+
- **`Deps` Dataclass**: Encapsulates dependencies injected into the agent like the API keys and shared HTTP client.
|
195 |
+
- **Weather Agent**: Configures an OpenAI GPT-4o-mini agent with tools for geolocation and weather.
|
196 |
+
- **Tools**:
|
197 |
+
- `get_lat_lng`: Geocodes a location using a free Maps.co API. Implements retry with exponential backoff.
|
198 |
+
- `get_weather`: Fetches live weather info from Tomorrow.io using lat/lng.
|
199 |
+
- **Agent Runner**: Wraps the interaction to run asynchronously with injected dependencies.
|
200 |
+
- **Streamlit UI**: Captures user input, triggers agent execution, and displays response with `asyncio`.
|
201 |
+
""")
|
202 |
+
|
203 |
+
st.image("https://raw.githubusercontent.com/gridflowai/gridflowAI-datasets-icons/862001d5ac107780b38f96eca34cefcb98c7f3e3/AI-icons-images/get_weather_app.png",
|
204 |
+
caption="Agentic Weather App Flow",
|
205 |
+
use_column_width=True)
|
206 |
+
|
207 |
+
|
208 |
+
import os
|
209 |
+
import asyncio
|
210 |
+
import streamlit as st
|
211 |
+
from dataclasses import dataclass
|
212 |
+
from typing import Any
|
213 |
+
|
214 |
+
import logfire
|
215 |
+
from httpx import AsyncClient
|
216 |
+
from pydantic_ai import Agent, RunContext, ModelRetry
|
217 |
+
|
218 |
+
# Configure logfire
|
219 |
+
logfire.configure(send_to_logfire='if-token-present')
|
220 |
+
|
221 |
+
@dataclass
|
222 |
+
class Deps:
|
223 |
+
client: AsyncClient
|
224 |
+
weather_api_key: str | None
|
225 |
+
geo_api_key: str | None
|
226 |
+
|
227 |
+
weather_agent = Agent(
|
228 |
+
'openai:gpt-4o-mini',
|
229 |
+
system_prompt=(
|
230 |
+
'Be concise, reply with one sentence. '
|
231 |
+
'Use the `get_lat_lng` tool to get the latitude and longitude of the locations, '
|
232 |
+
'then use the `get_weather` tool to get the weather.'
|
233 |
+
),
|
234 |
+
deps_type=Deps,
|
235 |
+
retries=2,
|
236 |
+
)
|
237 |
+
|
238 |
+
# Create a single global AsyncClient instance
|
239 |
+
client = AsyncClient()
|
240 |
+
|
241 |
+
@weather_agent.tool
|
242 |
+
async def get_lat_lng(ctx: RunContext[Deps],
|
243 |
+
location_description: str,
|
244 |
+
max_retries: int = 5,
|
245 |
+
base_delay: int = 2) -> dict[str, float]:
|
246 |
+
"""Get the latitude and longitude of a location."""
|
247 |
+
|
248 |
+
if ctx.deps.geo_api_key is None:
|
249 |
+
return {'lat': 51.1, 'lng': -0.1} # Default to London
|
250 |
+
|
251 |
+
# Sets up API request parameters.
|
252 |
+
params = {'q': location_description, 'api_key': ctx.deps.geo_api_key}
|
253 |
+
|
254 |
+
# Loops for a maximum number of retries.
|
255 |
+
for attempt in range(max_retries):
|
256 |
+
try:
|
257 |
+
# Logs API call span with parameters.
|
258 |
+
with logfire.span('calling geocode API', params=params) as span:
|
259 |
+
|
260 |
+
# Sends async GET request.
|
261 |
+
r = await ctx.deps.client.get('https://geocode.maps.co/search', params=params)
|
262 |
+
|
263 |
+
# Checks if API rate limit is exceeded.
|
264 |
+
if r.status_code == 429: # Too Many Requests
|
265 |
+
wait_time = base_delay * (2 ** attempt) # Exponential backoff
|
266 |
+
print(f"Rate limited. Retrying in {wait_time} seconds...")
|
267 |
+
|
268 |
+
# Waits before retrying.
|
269 |
+
await asyncio.sleep(wait_time)
|
270 |
+
|
271 |
+
# Continues to the next retry attempt.
|
272 |
+
continue # Retry the request
|
273 |
+
|
274 |
+
# Raises an exception for HTTP errors.
|
275 |
+
r.raise_for_status()
|
276 |
+
|
277 |
+
# Parses the API response as JSON.
|
278 |
+
data = r.json()
|
279 |
+
|
280 |
+
# Logs the response data.
|
281 |
+
span.set_attribute('response', data)
|
282 |
+
|
283 |
+
if data:
|
284 |
+
# Extracts and returns latitude & longitude.
|
285 |
+
return {'lat': float(data[0]['lat']), 'lng': float(data[0]['lon'])}
|
286 |
+
else:
|
287 |
+
# Raises an error if no valid data is found.
|
288 |
+
raise ModelRetry('Could not find the location')
|
289 |
+
|
290 |
+
except Exception as e: # Catches HTTP errors.
|
291 |
+
print(f"Request failed: {e}") # Logs the failure.
|
292 |
+
|
293 |
+
raise ModelRetry('Failed after multiple retries')
|
294 |
+
|
295 |
+
@weather_agent.tool
|
296 |
+
async def get_weather(ctx: RunContext[Deps], lat: float, lng: float) -> dict[str, Any]:
|
297 |
+
"""Get the weather at a location."""
|
298 |
+
if ctx.deps.weather_api_key is None:
|
299 |
+
return {'temperature': '21 °C', 'description': 'Sunny'}
|
300 |
+
params = {'apikey': ctx.deps.weather_api_key, 'location': f'{lat},{lng}', 'units': 'metric'}
|
301 |
+
|
302 |
+
r = await ctx.deps.client.get('https://api.tomorrow.io/v4/weather/realtime', params=params)
|
303 |
+
|
304 |
+
r.raise_for_status()
|
305 |
+
|
306 |
+
data = r.json()
|
307 |
+
|
308 |
+
values = data['data']['values']
|
309 |
+
|
310 |
+
code_lookup = {
|
311 |
+
1000: 'Clear, Sunny', 1001: 'Cloudy', 1100: 'Mostly Clear', 1101: 'Partly Cloudy',
|
312 |
+
1102: 'Mostly Cloudy', 2000: 'Fog', 2100: 'Light Fog', 4000: 'Drizzle', 4001: 'Rain',
|
313 |
+
4200: 'Light Rain', 4201: 'Heavy Rain', 5000: 'Snow', 5001: 'Flurries',
|
314 |
+
5100: 'Light Snow', 5101: 'Heavy Snow', 6000: 'Freezing Drizzle', 6001: 'Freezing Rain',
|
315 |
+
6200: 'Light Freezing Rain', 6201: 'Heavy Freezing Rain', 7000: 'Ice Pellets',
|
316 |
+
7101: 'Heavy Ice Pellets', 7102: 'Light Ice Pellets', 8000: 'Thunderstorm',
|
317 |
+
}
|
318 |
+
return {
|
319 |
+
'temperature': f'{values["temperatureApparent"]:0.0f}°C',
|
320 |
+
'description': code_lookup.get(values['weatherCode'], 'Unknown'),
|
321 |
+
}
|
322 |
+
|
323 |
+
async def run_weather_agent(user_input: str):
|
324 |
+
deps = Deps(
|
325 |
+
client=client, # Use global client
|
326 |
+
weather_api_key=os.getenv("TOMORROW_IO_API_KEY"),
|
327 |
+
geo_api_key=os.getenv("GEOCODE_API_KEY")
|
328 |
+
)
|
329 |
+
result = await weather_agent.run(user_input, deps=deps)
|
330 |
+
return result.data
|
331 |
+
|
332 |
+
# Initialize session state for storing weather responses
|
333 |
+
if "weather_response" not in st.session_state:
|
334 |
+
st.session_state.weather_response = None
|
335 |
+
|
336 |
+
# Set the page title
|
337 |
+
#st.set_page_config(page_title="Weather Application", page_icon="🚀")
|
338 |
+
|
339 |
+
# Streamlit UI
|
340 |
+
with st.expander(f"**Example prompts**"):
|
341 |
+
|
342 |
+
st.markdown(f"""
|
343 |
+
|
344 |
+
Prompt : If I were in Sydney today, would I need a jacket?
|
345 |
+
Bot : No, you likely wouldn't need a jacket as it's clear and sunny with a temperature of 22°C in Sydney.
|
346 |
+
|
347 |
+
Prompt : Tell me whether it's beach weather in Bali and Phuket.
|
348 |
+
Bot : Bali is too cold at 7°C and partly cloudy for beach weather, while Phuket is warm at 26°C with drizzle, making it more suitable for beach activities.
|
349 |
+
|
350 |
+
Prompt : If I had a meeting in Dubai, should I wear light clothing?
|
351 |
+
Bot : Yes, you should wear light clothing as the temperature in Dubai is currently 25°C and mostly clear.
|
352 |
+
|
353 |
+
Prompt : How does today’s temperature in Tokyo compare to the same time last week?
|
354 |
+
Bot : Today's temperature in Tokyo is 14°C, which is the same as the temperature at the same time last week.
|
355 |
+
|
356 |
+
Prompt : Is the current weather suitable for air travel in London and New York?
|
357 |
+
Bot : The current weather in London is 5°C and cloudy, and in New York, it is -0°C and clear; both conditions are generally suitable for air travel.
|
358 |
+
|
359 |
+
""")
|
360 |
+
|
361 |
+
user_input = st.text_area("Enter a sentence with locations:", "What is the weather like in Bangalore, Chennai and Delhi?")
|
362 |
+
|
363 |
+
# Button to trigger weather fetch
|
364 |
+
if st.button("Get Weather"):
|
365 |
+
with st.spinner("Fetching weather..."):
|
366 |
+
loop = asyncio.new_event_loop()
|
367 |
+
asyncio.set_event_loop(loop)
|
368 |
+
response = loop.run_until_complete(run_weather_agent(user_input))
|
369 |
+
st.session_state.weather_response = response
|
370 |
+
|
371 |
+
# Display stored response
|
372 |
+
if st.session_state.weather_response:
|
373 |
+
st.info(st.session_state.weather_response)
|
374 |
+
|
375 |
+
|
376 |
+
with st.expander("🧠 How is this app Agentic?"):
|
377 |
+
st.markdown("""
|
378 |
+
###### ✅ How this App is Agentic
|
379 |
+
|
380 |
+
This weather app demonstrates **Agentic AI** because:
|
381 |
+
|
382 |
+
1. **Goal-Oriented Autonomy**
|
383 |
+
The user provides a natural language request (e.g., *“What’s the weather in Bangalore and Delhi?”*).
|
384 |
+
The agent autonomously figures out *how* to fulfill it.
|
385 |
+
|
386 |
+
2. **Tool Usage by the Agent**
|
387 |
+
The `Agent` uses two tools:
|
388 |
+
- `get_lat_lng()` – to fetch coordinates via a geocoding API.
|
389 |
+
- `get_weather()` – to get real-time weather for those coordinates.
|
390 |
+
The agent determines when and how to use these tools.
|
391 |
+
|
392 |
+
3. **Context + Dependency Injection**
|
393 |
+
The app uses the `Deps` dataclass to provide the agent with shared dependencies like HTTP clients and API keys—just like a human agent accessing internal tools.
|
394 |
+
|
395 |
+
4. **Retries and Adaptive Behavior**
|
396 |
+
The agent handles failures and retries via `ModelRetry`, showing resilience and smart retry logic.
|
397 |
+
|
398 |
+
5. **Structured Interactions via `RunContext`**
|
399 |
+
Each tool runs with access to structured context, enabling better coordination and reuse of shared state.
|
400 |
+
|
401 |
+
6. **LLM-Orchestrated Actions**
|
402 |
+
At the core, a GPT-4o-mini model orchestrates:
|
403 |
+
- Understanding the user intent,
|
404 |
+
- Selecting and invoking the right tools,
|
405 |
+
- Synthesizing the final response.
|
406 |
+
|
407 |
+
> 🧠 **In essence**: This is not just a chatbot, but an *autonomous reasoning engine* that uses real tools to complete real-world goals.
|
408 |
+
""")
|
409 |
+
|
410 |
+
with st.expander("🧪 Example Prompts: Handling Complex Queries"):
|
411 |
+
st.markdown("""
|
412 |
+
This app can understand **natural, varied, and multi-part prompts** thanks to the LLM-based agent at its core.
|
413 |
+
It intelligently uses `get_lat_lng()` and `get_weather()` tools based on user intent.
|
414 |
+
|
415 |
+
###### 🗣️ Complex Prompt Examples & Responses:
|
416 |
+
|
417 |
+
**Prompt:**
|
418 |
+
*If I were in Sydney today, would I need a jacket?*
|
419 |
+
**Response:**
|
420 |
+
*No, you likely wouldn't need a jacket as it's clear and sunny with a temperature of 22°C in Sydney.*
|
421 |
+
|
422 |
+
---
|
423 |
+
|
424 |
+
**Prompt:**
|
425 |
+
*Tell me whether it's beach weather in Bali and Phuket.*
|
426 |
+
**Response:**
|
427 |
+
*Bali is too cold at 7°C and partly cloudy for beach weather, while Phuket is warm at 26°C with drizzle, making it more suitable for beach activities.*
|
428 |
+
|
429 |
+
---
|
430 |
+
|
431 |
+
**Prompt:**
|
432 |
+
*If I had a meeting in Dubai, should I wear light clothing?*
|
433 |
+
**Response:**
|
434 |
+
*Yes, you should wear light clothing as the temperature in Dubai is currently 25°C and mostly clear.*
|
435 |
+
|
436 |
+
---
|
437 |
+
|
438 |
+
**Prompt:**
|
439 |
+
*How does today’s temperature in Tokyo compare to the same time last week?*
|
440 |
+
**Response:**
|
441 |
+
*Today's temperature in Tokyo is 14°C, which is the same as the temperature at the same time last week.*
|
442 |
+
*(Note: This would require historical API support to be accurate in a real app.)*
|
443 |
+
|
444 |
+
---
|
445 |
+
|
446 |
+
**Prompt:**
|
447 |
+
*Is the current weather suitable for air travel in London and New York?*
|
448 |
+
**Response:**
|
449 |
+
*The current weather in London is 5°C and cloudy, and in New York, it is -0°C and clear; both conditions are generally suitable for air travel.*
|
450 |
+
|
451 |
+
---
|
452 |
+
|
453 |
+
**Prompt:**
|
454 |
+
*Give me the weather update for all cities where cricket matches are happening today in India.*
|
455 |
+
**Response:**
|
456 |
+
*(This would involve external logic for identifying cricket venues, but the agent can handle the weather lookup part once cities are known.)*
|
457 |
+
|
458 |
+
---
|
459 |
+
|
460 |
+
###### 🧠 Why it Works:
|
461 |
+
- The **agent extracts all cities** from the prompt, even if mixed with unrelated text.
|
462 |
+
- It **chains tool calls**: First gets geolocation, then weather.
|
463 |
+
- The **final response is LLM-crafted** to match the tone and question format (yes/no, suggestion, comparison, etc.).
|
464 |
+
|
465 |
+
> ✅ You don’t need to ask "what's the weather in X" exactly — the agent infers it from how humans speak.
|
466 |
+
""")
|
467 |
+
|
468 |
+
with st.expander("🔍 Missing Agentic AI Capabilities & How to Improve"):
|
469 |
+
st.markdown("""
|
470 |
+
While the app exhibits several **agentic behaviors**—like tool use, intent recognition, and multi-step reasoning—it still lacks **some core features** found in *fully agentic systems*. Here's what’s missing:
|
471 |
+
|
472 |
+
###### ❌ Missing Facets & How to Add Them
|
473 |
+
|
474 |
+
**1. Autonomy & Proactive Behavior**
|
475 |
+
*Current:* The app only responds to user prompts.
|
476 |
+
*To Add:* Let the agent proactively ask follow-ups.
|
477 |
+
**Example:**
|
478 |
+
- User: *What's the weather in Italy?*
|
479 |
+
- Agent: *Italy has multiple cities. Would you like weather in Rome, Milan, or Venice?*
|
480 |
+
|
481 |
+
**2. Goal-Oriented Planning**
|
482 |
+
*Current:* Executes one tool or a fixed chain of tools.
|
483 |
+
*To Add:* Give it a higher-level goal and let it plan the steps.
|
484 |
+
**Example:**
|
485 |
+
- Prompt: *Help me plan a weekend trip to a warm place in Europe.*
|
486 |
+
- Agent: Finds warm cities, checks weather, compares, and recommends.
|
487 |
+
|
488 |
+
**3. Memory / Session Context**
|
489 |
+
*Current:* Stateless; each query is standalone.
|
490 |
+
*To Add:* Use LangGraph or crewAI memory modules to **remember past queries** or preferences.
|
491 |
+
**Example:**
|
492 |
+
- User: *What’s the weather in Delhi?*
|
493 |
+
- Then: *And how about tomorrow?* → Agent should know the context refers to Delhi.
|
494 |
+
|
495 |
+
**4. Delegation to Sub-Agents**
|
496 |
+
*Current:* Single-agent, monolithic logic.
|
497 |
+
*To Add:* Delegate tasks to specialized agents (geocoder agent, weather formatter agent, response stylist, etc.).
|
498 |
+
**Example:**
|
499 |
+
- Planner agent decides cities → Fetcher agent retrieves data → Explainer agent summarizes.
|
500 |
+
|
501 |
+
**5. Multi-Modal Input/Output**
|
502 |
+
*Current:* Only text.
|
503 |
+
*To Add:* Accept voice prompts or generate a weather infographic.
|
504 |
+
**Example:**
|
505 |
+
- Prompt: *Voice note saying "Is it rainy in London?"* → Returns image with rainy clouds and summary.
|
506 |
+
|
507 |
+
**6. Learning from Feedback**
|
508 |
+
*Current:* No learning or improvement from user input.
|
509 |
+
*To Add:* Allow thumbs up/down or feedback to tune responses.
|
510 |
+
**Example:**
|
511 |
+
- User: *That was not helpful.* → Agent: *Sorry! Want a more detailed report or city breakdown?*
|
512 |
+
|
513 |
+
---
|
514 |
+
|
515 |
+
###### ✅ Summary
|
516 |
+
This app **lays a strong foundation for Agentic AI**, but adding these elements would bring it closer to a **truly autonomous, context-aware, and planning-capable agent** that mimics human-level task execution.
|
517 |
+
""")
|
518 |
+
|
519 |
+
|
520 |
+
|
521 |
+
|
522 |
+
|
523 |
+
|
524 |
+
|
525 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
openai
|
2 |
+
httpx
|
3 |
+
pydantic
|
4 |
+
pydantic-ai
|
5 |
+
logfire
|
6 |
+
python-dotenv
|