SHAMIL SHAHBAZ AWAN commited on
Commit
e22a015
·
verified ·
1 Parent(s): 15da5d8

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +362 -0
app.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import json
3
+ import os
4
+ import time
5
+ from langchain_core.messages import SystemMessage, HumanMessage
6
+ from langchain_community.tools.tavily_search import TavilySearchResults
7
+ from langchain_community.document_loaders import WebBaseLoader
8
+ from langchain_community.vectorstores import FAISS
9
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
10
+ from langchain.tools.retriever import create_retriever_tool
11
+ from langgraph.graph import StateGraph, START
12
+ from langgraph.checkpoint.memory import MemorySaver
13
+ from langgraph.graph.state import CompiledStateGraph
14
+ from langgraph.graph import MessagesState
15
+ from langgraph.prebuilt import ToolNode
16
+ from langgraph.prebuilt import tools_condition
17
+ from dotenv import load_dotenv
18
+ from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
19
+ from langchain_community.tools.tavily_search import TavilySearchResults
20
+ from langgraph.graph import END, StateGraph
21
+ from nasapy import Nasa
22
+ from pydantic import BaseModel
23
+ import requests
24
+ import streamlit as st
25
+ import tavily
26
+
27
+ # Load environment variables
28
+ load_dotenv()
29
+
30
+ # Initialize models and APIs
31
+ llm = ChatGoogleGenerativeAI(
32
+ model="gemini-1.5-flash",
33
+ google_api_key=os.getenv("GOOGLE_API_KEY")
34
+ )
35
+
36
+ def space_events_agent():
37
+ """
38
+ Fetches and formats upcoming space events from NASA's EONET API.
39
+
40
+ Args:
41
+ state (AgentState): The current agent state.
42
+
43
+ Returns:
44
+ dict: A dictionary with the formatted space events or a message if no events are found.
45
+
46
+ Raises:
47
+ HTTPError: If the request to the NASA API fails.
48
+
49
+ Note:
50
+ Requires NASA API key set in the environment variable `NASA_API_KEY`.
51
+ """
52
+ base_url = "https://eonet.gsfc.nasa.gov/api/v2.1/events"
53
+ # Using a separate NASA API key for EONET if needed
54
+ params = {"api_key": os.getenv("NASA_API_KEY")}
55
+ response = requests.get(base_url, params=params)
56
+ response.raise_for_status()
57
+ events = response.json().get("events", [])
58
+ if events:
59
+ event_details = []
60
+ for event in events:
61
+ # Safely retrieve category and coordinates
62
+ category = (event.get('categories', [{}])[0]).get('title', 'Unknown Category')
63
+ geometries = event.get('geometries', [])
64
+ coordinates = geometries[0].get('coordinates', 'Unknown Location') if geometries else 'Unknown Location'
65
+ event_details.append(
66
+ f"• Event: {event.get('title', 'N/A')}\n"
67
+ f" Type: {category}\n"
68
+ f" Location: {coordinates}\n"
69
+ )
70
+ formatted_events = "\n".join(event_details)
71
+ return {"output": f"🚀 Space Events Agent:\nn{formatted_events}"}
72
+ else:
73
+ return {"output": f"🚀 Space Events Agent:No events found at the moment."}
74
+
75
+ loader1 = WebBaseLoader("https://spacelaunchnow.me/3.3.0/launch/upcoming/")
76
+ docs1 = loader1.load()
77
+ documents = RecursiveCharacterTextSplitter(
78
+ chunk_size=1000, chunk_overlap=200
79
+ ).split_documents(docs1)
80
+ vector = FAISS.from_documents(documents, GoogleGenerativeAIEmbeddings(model="models/embedding-001"))
81
+
82
+ retriever = vector.as_retriever()
83
+ retriever_tool = create_retriever_tool(
84
+ retriever,
85
+ "spacelaunchnow",
86
+ "Search for information about space real time news",
87
+ )
88
+
89
+ def astronomy_image_agent(user_input: str):
90
+ """
91
+ Retrieves astronomy-related images from NASA's Image and Video Library or the Astronomy Picture of the Day (APOD) API.
92
+
93
+ If the user query contains "galaxy", it fetches galaxy images from NASA's Image and Video Library.
94
+ If no specific keyword is found, it defaults to fetching the Astronomy Picture of the Day (APOD).
95
+
96
+ Args:
97
+ user_input (str): The user input containing the query.
98
+
99
+ Returns:
100
+ dict: A dictionary containing details about the requested astronomy image,
101
+ including the title, description, and URL. If an error occurs, an error message is returned.
102
+
103
+ Raises:
104
+ HTTPError: If the request to the NASA API fails.
105
+ ValueError: If no relevant image or data is found.
106
+ """
107
+ user_input = user_input.lower()
108
+ if "galaxy" in user_input:
109
+ # Query NASA's Image and Video Library API for galaxy images.
110
+ search_url = "https://images-api.nasa.gov/search"
111
+ params = {"q": "galaxy", "media_type": "image"}
112
+ try:
113
+ response = requests.get(search_url, params=params)
114
+ response.raise_for_status()
115
+ data = response.json()
116
+ items = data.get("collection", {}).get("items", [])
117
+ if items:
118
+ first_item = items[0]
119
+ links = first_item.get("links", [])
120
+ image_url = links[0]["href"] if links else "No URL available"
121
+ title = first_item.get("data", [{}])[0].get("title", "No Title")
122
+ description = first_item.get("data", [{}])[0].get("description", "No Description")
123
+ result = (
124
+ f"Title: {title}\n"
125
+ f"Description: {description}\n"
126
+ f"Image URL: {image_url}"
127
+ )
128
+ return {"output": f"✨ Galaxy Image Agent:\n\n{result}"}
129
+ else:
130
+ return {"output": "No galaxy images found."}
131
+ except Exception as e:
132
+ return {"output": f"Galaxy image search failed: {str(e)}"}
133
+ else:
134
+ # Default to the APOD API if no keyword is detected.
135
+ url = f"https://api.nasa.gov/planetary/apod?api_key={os.getenv('NASA_API_KEY')}"
136
+ try:
137
+ response = requests.get(url)
138
+ response.raise_for_status()
139
+ data = response.json()
140
+ result = (
141
+ f"Title: {data.get('title', 'N/A')}\n"
142
+ f"Date: {data.get('date', 'N/A')}\n"
143
+ f"Explanation: {data.get('explanation', 'N/A')}\n"
144
+ f"Image URL: {data.get('url', 'N/A')}\n"
145
+ f"Media Type: {data.get('media_type', 'N/A')}\n"
146
+ f"Copyright: {data.get('copyright', 'Public Domain')}"
147
+ )
148
+ return {"output": f"✨ Astronomy Image Agent:\n\n{result}"}
149
+ except Exception as e:
150
+ return {"output": f"Astronomy Image Agent Error: {str(e)}"}
151
+
152
+ def educational_resources_agent():
153
+ """
154
+ Retrieves and formats a list of high-quality educational resources on astronomy and space science.
155
+ Uses Tavily search to find trusted sources such as NASA, ESA, and other reputable platforms.
156
+
157
+ Args:
158
+ state (AgentState): The current agent state, containing user input.
159
+
160
+ Returns:
161
+ dict: A dictionary containing a list of educational resources with titles, descriptions, and clickable links.
162
+
163
+ Raises:
164
+ Exception: If an error occurs while retrieving the resources or formatting the response.
165
+ """
166
+ try:
167
+ # Use Tavily search to retrieve educational resources based on the query
168
+ resources = search.invoke({"query": "Provide a curated list of high-quality educational resources on astronomy and space science, from trusted sources like NASA, ESA, and reputable educational platforms."})
169
+ if not resources:
170
+ return {"output": "No educational resources found. Please try again."}
171
+
172
+ formatted_resources = "\n\n".join(
173
+ [f"📘 {r.get('title', 'Untitled Resource')}\n🔗 {r.get('url', 'No URL provided')}" for r in resources]
174
+ )
175
+ return {"output": f"🎓 Educational Resources Agent:\n\n{formatted_resources}"}
176
+ except Exception as e:
177
+ return {"output": f"Educational Resources Agent Error: {str(e)}"}
178
+
179
+ def iss_location_agent():
180
+ """
181
+ Fetches astronaut data and the current position of the International Space Station (ISS).
182
+
183
+ It fetches the list of astronauts currently on the ISS, and retrieves the current position of the ISS.
184
+ The data is updated at intervals and returned in a structured format.
185
+
186
+ Returns:
187
+ dict: A dictionary containing astronaut information and ISS position updates.
188
+ Includes a list of people on the ISS and the positions over time.
189
+ """
190
+ # Fetch astronaut data
191
+ api_url = "http://api.open-notify.org/astros.json"
192
+ data = requests.get(api_url).json()
193
+
194
+ result = {}
195
+
196
+ if data['message'] == 'success':
197
+ with open('iss-astros.json', 'w') as iss_file:
198
+ json.dump(data, iss_file, indent=4)
199
+
200
+ result["astronauts"] = f"There are currently {data['number']} people on the ISS."
201
+ result["people"] = []
202
+ for person in data['people']:
203
+ result["people"].append(f"{person['name']} is currently on the ISS, Craft: {person['craft']}")
204
+ else:
205
+ result["error"] = 'Failed to obtain astronauts data.'
206
+
207
+ # List to hold ISS position updates
208
+ positions = []
209
+
210
+ # fetching ISS position data
211
+ for attempt in range(5): # Retry 5 times
212
+ try:
213
+ data = requests.get("http://api.open-notify.org/iss-now.json", timeout=10).json()
214
+ if data['message'] == 'success':
215
+ # Extract ISS location
216
+ location = data["iss_position"]
217
+ latitude = float(location['latitude'])
218
+ longitude = float(location['longitude'])
219
+
220
+ position = {
221
+ "latitude": latitude,
222
+ "longitude": longitude,
223
+ "time": str(datetime.datetime.fromtimestamp(data['timestamp']))
224
+ }
225
+ positions.append(position)
226
+ break # Successfully fetched data, break the loop
227
+ except requests.exceptions.RequestException as e:
228
+ result["error"] = str(e)
229
+ time.sleep(3) # Wait for 3 seconds before retrying
230
+
231
+ result["positions"] = positions if positions else "Failed to retrieve ISS position data after retries."
232
+ return result
233
+
234
+ search = TavilySearchResults(tavily_api_key=os.getenv("TAVILY_API_KEY"))
235
+
236
+ tools = [search, iss_location_agent, space_events_agent, astronomy_image_agent, educational_resources_agent]
237
+ llm_with_tools = llm.bind_tools(tools)
238
+
239
+ # System message
240
+ sys_msg = SystemMessage(content='''This system is designed to provide real-time astronomical data, visualizations, and educational content. Below are the key functions and tools integrated into the system and their specific purposes:
241
+
242
+ 1. **Tavily Search (`search`) Integration**:
243
+ - **Purpose**: Provides users with up-to-date, relevant space-related information from Tavily's extensive search engine.
244
+ - **Usage**: Enables the assistant to fetch the latest news, research articles, and educational resources related to space.
245
+
246
+ 2. **NASA APOD Tool (`get_nasa_apod_tool`)**:
247
+ - **Purpose**: Fetches the Astronomy Picture of the Day (APOD) from NASA's APOD API.
248
+ - **Usage**: Provides the title, explanation, and image URL of the latest astronomy image shared by NASA, offering users daily insights into the wonders of space.
249
+
250
+ 3. **NASA EONET Space Events (`fetch_nasa_eonet_events`)**:
251
+ - **Purpose**: Retrieves real-time space-related events from NASA’s EONET API.
252
+ - **Usage**: Provides details about ongoing or upcoming space events, including event type, title, location, and date, offering users information about significant celestial events.
253
+
254
+ 4. **ISS Location Plotting (`plot_iss_location`)**:
255
+ - **Purpose**: Displays the current real-time location of the International Space Station (ISS).
256
+ - **Usage**: Visualizes the ISS position on a latitude-longitude graph, allowing users to track its orbit across the globe in real-time.
257
+
258
+ 5. **ISS Astronaut Data (`ISS_data`)**:
259
+ - **Purpose**: Provides data about astronauts currently aboard the ISS and their associated spacecraft.
260
+ - **Usage**: Retrieves information on the number of astronauts aboard, their spacecraft, and visualizes the ISS’s position on a world map.
261
+
262
+ 6. **Space Educational Fact (`educational_space_fact`)**:
263
+ - **Purpose**: Fetches random educational facts about space from the Space Trivia API.
264
+ - **Usage**: Provides fascinating trivia about celestial bodies, space phenomena, and other space-related topics, helping to educate users in an engaging manner.
265
+
266
+ 7. **NEO Data Fetcher (`get_neo_data`)**:
267
+ - **Purpose**: Retrieves real-time data on Near-Earth Objects (NEOs) from NASA’s NEO API.
268
+ - **Usage**: Provides information on potentially hazardous asteroids and comets, helping users stay informed about objects that come close to Earth.
269
+ 8. **Space News Agent (`retriever_tool`)**:
270
+ - **Purpose**: Fetches the latest space news articles.
271
+ - **Usage**: Provides users with up-to-date news articles related to space exploration, satellite launches, and astronomical discoveries.
272
+
273
+ ### Workflow:
274
+ - The system responds dynamically to user queries by identifying the appropriate tool for each request.
275
+ - If the user asks for data or visualizations related to astronomical objects or space events, the relevant NASA APIs or visualization tools are invoked.
276
+ - For general space education or research, tools like Tavily and Space Trivia provide comprehensive, up-to-date educational content.
277
+ - The system remembers the context of conversations to ensure relevant and seamless interactions with users, supported by the `MemorySaver`.
278
+
279
+ ### Guidelines:
280
+ - **Scientific Accuracy**: All provided information is scientifically accurate and up-to-date.
281
+ - **Engagement**: Educational content is easy to understand and presented in an interactive and engaging way.
282
+ - **Visualization**: For requests requiring visual representations (such as ISS tracking or NEO data), interactive orbital plots or maps are rendered for clarity.
283
+ - **Tool Alignment**: The system uses the most appropriate tool based on the user’s request, ensuring minimal invocation of unnecessary functions.
284
+
285
+ This setup ensures that the assistant can efficiently address a wide range of user queries related to space, astronomy, and related educational content while keeping interactions intuitive and engaging.''')
286
+
287
+
288
+ # Node
289
+ def assistant(state: MessagesState) -> MessagesState:
290
+ return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"][-10:])]}
291
+
292
+ # Build graph
293
+ builder: StateGraph = StateGraph(MessagesState)
294
+
295
+ # Define nodes: these do the work
296
+ builder.add_node("assistant", assistant)
297
+ builder.add_node("tools", ToolNode(tools))
298
+
299
+ # Define edges: these determine how the control flow moves
300
+ builder.add_edge(START, "assistant")
301
+ builder.add_conditional_edges(
302
+ "assistant",
303
+ # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools
304
+ # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END
305
+ tools_condition,
306
+ )
307
+ builder.add_edge("tools", "assistant")
308
+ memory: MemorySaver = MemorySaver()
309
+ # Compile the workflow into an application
310
+ react_graph_memory: CompiledStateGraph = builder.compile(checkpointer=memory)
311
+
312
+
313
+ st.title("🚀 Space Agentic Chatbot")
314
+ st.caption("Your AI-powered space exploration assistant")
315
+
316
+ if "messages" not in st.session_state:
317
+ st.session_state.messages = []
318
+
319
+ # Display existing chat messages
320
+ for message in st.session_state.messages:
321
+ with st.chat_message(message["role"]):
322
+ if message["content"].startswith("http"):
323
+ st.image(message["content"])
324
+ else:
325
+ st.write(message["content"])
326
+
327
+ # User input via chat
328
+ if user_input := st.chat_input("Ask about space news, ISS location, or astronomy images"):
329
+ st.session_state.messages.append({"role": "user", "content": user_input})
330
+ st.chat_message("user").write(user_input)
331
+
332
+ try:
333
+ # Prepare the messages for the model
334
+ messages = [HumanMessage(content=user_input)]
335
+
336
+ # Call the memory and invoke the response
337
+ response = react_graph_memory.invoke({"messages": messages}, config={"configurable": {"thread_id": "1"}})
338
+ assistant_response = response["messages"][-1].content
339
+
340
+ # Handle image URLs and regular text responses
341
+ if "image url:" in assistant_response.lower():
342
+ image_url = assistant_response.split("Image URL:")[1].split("\n")[0].strip()
343
+
344
+ # Store the image URL and the response message in session state
345
+ st.session_state.messages.append({"role": "assistant", "content": image_url})
346
+ st.session_state.messages.append({"role": "assistant", "content": assistant_response})
347
+ with st.chat_message("assistant"):
348
+ st.image(image_url) # Display the image
349
+ st.write(assistant_response) # Display the accompanying text
350
+
351
+ else:
352
+ # If no image URL, just store the regular text response
353
+ st.session_state.messages.append({"role": "assistant", "content": assistant_response})
354
+ with st.chat_message("assistant"):
355
+ st.write(assistant_response)
356
+
357
+ except Exception as e:
358
+ error_msg = f"Error processing request: {str(e)}"
359
+ # Store the error message in session state
360
+ st.session_state.messages.append({"role": "assistant", "content": error_msg})
361
+ with st.chat_message("assistant"):
362
+ st.write(error_msg)