SHAMIL SHAHBAZ AWAN commited on
Commit
a1b3907
·
verified ·
1 Parent(s): 7400afc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +147 -209
app.py CHANGED
@@ -2,32 +2,31 @@ 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")
@@ -36,77 +35,61 @@ llm = ChatGoogleGenerativeAI(
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:
@@ -121,197 +104,157 @@ def astronomy_image_agent(user_input: str):
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 = []
@@ -319,44 +262,39 @@ if "messages" not in st.session_state:
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)
 
2
  import json
3
  import os
4
  import time
5
+ import re
6
+ import requests
7
+ import streamlit as st
8
+ from dotenv import load_dotenv
9
+ from pydantic import BaseModel
10
+
11
+ # LangChain, LangGraph, and related imports
12
  from langchain_core.messages import SystemMessage, HumanMessage
13
  from langchain_community.tools.tavily_search import TavilySearchResults
14
  from langchain_community.document_loaders import WebBaseLoader
15
  from langchain_community.vectorstores import FAISS
16
  from langchain_text_splitters import RecursiveCharacterTextSplitter
17
  from langchain.tools.retriever import create_retriever_tool
 
 
 
 
 
 
 
18
  from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
19
+ from langgraph.graph import StateGraph, START, END
20
+ from langgraph.graph.state import MessagesState
21
+ from langgraph.prebuilt import ToolNode, tools_condition
22
+ from langgraph.checkpoint.memory import MemorySaver
23
+ from langgraph.graph import CompiledStateGraph
 
24
  import tavily
25
 
26
  # Load environment variables
27
  load_dotenv()
28
 
29
+ # Initialize the LLM (using Gemini-1.5 Flash) with Google API key from env variables
30
  llm = ChatGoogleGenerativeAI(
31
  model="gemini-1.5-flash",
32
  google_api_key=os.getenv("GOOGLE_API_KEY")
 
35
  def space_events_agent():
36
  """
37
  Fetches and formats upcoming space events from NASA's EONET API.
 
 
 
 
38
  Returns:
39
+ dict: A dictionary with a detailed formatted message of the current space events.
 
 
 
 
 
 
40
  """
41
  base_url = "https://eonet.gsfc.nasa.gov/api/v2.1/events"
 
42
  params = {"api_key": os.getenv("NASA_API_KEY")}
43
+ try:
44
+ response = requests.get(base_url, params=params)
45
+ response.raise_for_status()
46
+ events = response.json().get("events", [])
47
+ except Exception as e:
48
+ return {"output": f"🚀 Space Events Agent Error: Unable to fetch events. Details: {str(e)}"}
49
+
50
  if events:
51
  event_details = []
52
  for event in events:
 
53
  category = (event.get('categories', [{}])[0]).get('title', 'Unknown Category')
54
  geometries = event.get('geometries', [])
55
  coordinates = geometries[0].get('coordinates', 'Unknown Location') if geometries else 'Unknown Location'
56
+ # Include additional detailed formatting for each event
57
  event_details.append(
58
+ f"• **Event:** {event.get('title', 'N/A')}\n"
59
+ f" **Type:** {category}\n"
60
+ f" **Location (Lon, Lat):** {coordinates}\n"
61
  )
62
  formatted_events = "\n".join(event_details)
63
+ detailed_message = (
64
+ f"🚀 **Space Events Agent:**\n\n"
65
+ f"Here are the latest space events with detailed information:\n\n{formatted_events}\n"
66
+ f"*(Data retrieved from NASA's EONET API)*"
67
+ )
68
+ return {"output": detailed_message}
69
  else:
70
+ return {"output": "🚀 Space Events Agent: No events found at the moment. Please check back later for updates."}
71
 
72
+ # Load upcoming launches data and prepare a retriever for space news
73
  loader1 = WebBaseLoader("https://spacelaunchnow.me/3.3.0/launch/upcoming/")
74
  docs1 = loader1.load()
75
+ documents = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200).split_documents(docs1)
 
 
76
  vector = FAISS.from_documents(documents, GoogleGenerativeAIEmbeddings(model="models/embedding-001"))
 
77
  retriever = vector.as_retriever()
78
  retriever_tool = create_retriever_tool(
79
  retriever,
80
  "spacelaunchnow",
81
+ "Search for up-to-date space launch news and real time information"
82
  )
83
 
84
  def astronomy_image_agent(user_input: str):
85
  """
86
+ Retrieves astronomy-related images. If the query mentions 'galaxy', it fetches an image from
87
+ NASA's Image and Video Library; otherwise it fetches NASA’s Astronomy Picture of the Day (APOD).
 
 
 
 
 
 
88
  Returns:
89
+ dict: A dictionary with detailed image information including title, explanation, and image URL.
 
 
 
 
 
90
  """
91
  user_input = user_input.lower()
92
  if "galaxy" in user_input:
 
93
  search_url = "https://images-api.nasa.gov/search"
94
  params = {"q": "galaxy", "media_type": "image"}
95
  try:
 
104
  title = first_item.get("data", [{}])[0].get("title", "No Title")
105
  description = first_item.get("data", [{}])[0].get("description", "No Description")
106
  result = (
107
+ f"**Title:** {title}\n"
108
+ f"**Description:** {description}\n"
109
+ f"**Image URL:** {image_url}\n\n"
110
+ f"This image was retrieved from NASA's Image and Video Library."
111
  )
112
+ return {"output": f"✨ **Galaxy Image Agent:**\n\n{result}"}
113
  else:
114
+ return {"output": "✨ Galaxy Image Agent: No galaxy images found. Please try another query."}
115
  except Exception as e:
116
+ return {"output": f"Galaxy Image Agent Error: {str(e)}"}
117
  else:
118
+ # Default to Astronomy Picture of the Day (APOD)
119
  url = f"https://api.nasa.gov/planetary/apod?api_key={os.getenv('NASA_API_KEY')}"
120
  try:
121
  response = requests.get(url)
122
  response.raise_for_status()
123
  data = response.json()
124
  result = (
125
+ f"**Title:** {data.get('title', 'N/A')}\n"
126
+ f"**Date:** {data.get('date', 'N/A')}\n"
127
+ f"**Explanation:** {data.get('explanation', 'N/A')}\n"
128
+ f"**Image URL:** {data.get('url', 'N/A')}\n"
129
+ f"**Media Type:** {data.get('media_type', 'N/A')}\n"
130
+ f"**Copyright:** {data.get('copyright', 'Public Domain')}\n\n"
131
+ f"This Astronomy Picture of the Day is provided by NASA's APOD API and is updated daily."
132
  )
133
+ return {"output": f"✨ **Astronomy Image Agent:**\n\n{result}"}
134
  except Exception as e:
135
+ return {"output": f"Astronomy Image Agent Error: {str(e)}"}
136
 
137
  def educational_resources_agent():
138
  """
139
+ Retrieves and formats a list of high-quality educational resources on astronomy and space science.
140
+ Uses Tavily search to fetch resources from trusted sources.
 
 
 
 
141
  Returns:
142
+ dict: A dictionary containing a detailed list of curated educational resources.
 
 
 
143
  """
144
  try:
145
+ 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."})
 
146
  if not resources:
147
+ return {"output": "🎓 Educational Resources Agent: No educational resources found. Please try again."}
148
 
149
  formatted_resources = "\n\n".join(
150
+ [f"📘 **{r.get('title', 'Untitled Resource')}**\n🔗 {r.get('url', 'No URL provided')}" for r in resources]
151
  )
152
+ detailed_message = (
153
+ f"🎓 **Educational Resources Agent:**\n\n"
154
+ f"Below is a curated list of high-quality educational resources on astronomy and space science. "
155
+ f"Click on the links for more detailed information:\n\n{formatted_resources}"
156
+ )
157
+ return {"output": detailed_message}
158
  except Exception as e:
159
+ return {"output": f"🎓 Educational Resources Agent Error: {str(e)}"}
160
 
161
  def iss_location_agent():
162
  """
163
+ Fetches detailed astronaut data and the current position of the International Space Station (ISS).
 
 
 
 
164
  Returns:
165
+ dict: A dictionary containing a detailed formatted message with astronaut information and ISS position updates.
 
166
  """
167
  # Fetch astronaut data
168
  api_url = "http://api.open-notify.org/astros.json"
169
+ try:
170
+ data = requests.get(api_url).json()
171
+ except Exception as e:
172
+ return {"output": f"🚀 ISS Location Agent Error: Unable to fetch astronaut data. Details: {str(e)}"}
173
+
174
+ output_lines = []
175
+ if data.get('message') == 'success':
176
+ astronauts_line = f"There are currently {data.get('number', 'N/A')} people in space."
177
+ people_lines = "\n".join([f"- {person['name']} aboard {person['craft']}" for person in data.get('people', [])])
178
+ output_lines.append(f"**Astronaut Information:**\n{astronauts_line}\n{people_lines}")
 
 
179
  else:
180
+ output_lines.append("Error: Failed to obtain astronaut data.")
181
+
182
+ # Fetch ISS current position data with retries
183
  positions = []
184
+ for attempt in range(5):
 
 
185
  try:
186
+ pos_data = requests.get("http://api.open-notify.org/iss-now.json", timeout=10).json()
187
+ if pos_data.get('message') == 'success':
188
+ location = pos_data.get("iss_position", {})
189
+ latitude = float(location.get('latitude', 0))
190
+ longitude = float(location.get('longitude', 0))
191
+ timestamp = datetime.datetime.fromtimestamp(pos_data.get('timestamp', 0)).strftime("%Y-%m-%d %H:%M:%S")
192
+ positions.append(f"- Latitude: {latitude}, Longitude: {longitude} (Recorded at: {timestamp})")
193
+ break
 
 
 
 
 
 
194
  except requests.exceptions.RequestException as e:
195
+ time.sleep(3)
196
+ if positions:
197
+ output_lines.append("**Current ISS Position:**\n" + "\n".join(positions))
198
+ else:
199
+ output_lines.append("Error: Failed to retrieve ISS position data after multiple attempts.")
200
+
201
+ detailed_message = (
202
+ f"🚀 **ISS Location Agent:**\n\n" +
203
+ "\n\n".join(output_lines) +
204
+ "\n\n*(Data retrieved from open-notify.org)*"
205
+ )
206
+ return {"output": detailed_message}
207
+
208
+ # Initialize Tavily search tool using the provided API key
209
  search = TavilySearchResults(tavily_api_key=os.getenv("TAVILY_API_KEY"))
210
 
211
+ # List of tools available to the assistant
212
  tools = [search, iss_location_agent, space_events_agent, astronomy_image_agent, educational_resources_agent]
213
  llm_with_tools = llm.bind_tools(tools)
214
 
215
+ # Updated system message instructing detailed and informative responses
216
+ sys_msg = SystemMessage(content='''This system is designed to provide real-time astronomical data, visualizations, and educational content. The assistant must generate responses that are detailed, informative, and rich in context. Below are the key functions and tools integrated into the system and their specific purposes:
217
 
218
  1. **Tavily Search (`search`) Integration**:
219
+ - **Purpose**: Provides up-to-date, relevant space-related information.
220
+ - **Usage**: Fetches the latest news, research articles, and educational resources related to space.
 
 
 
 
221
 
222
+ 2. **NASA APOD Tool**:
223
+ - **Purpose**: Fetches NASA’s Astronomy Picture of the Day (APOD).
224
+ - **Usage**: Provides detailed information including title, explanation, and image URL.
225
 
226
+ 3. **NASA EONET Space Events**:
227
+ - **Purpose**: Retrieves real-time space events.
228
+ - **Usage**: Provides detailed data on ongoing/upcoming space events.
229
 
230
+ 4. **ISS Location and Astronaut Data**:
231
+ - **Purpose**: Displays the current ISS location and astronaut information.
232
+ - **Usage**: Provides a detailed update on ISS coordinates and people in space.
233
 
234
+ 5. **Space Educational Resources**:
235
+ - **Purpose**: Curates educational resources on astronomy and space science.
236
+ - **Usage**: Provides a detailed list with titles and clickable links.
237
 
238
+ When responding to user queries, the assistant should provide comprehensive, detailed, and accurate explanations. Visual responses should include image URLs that the interface can use to display pictures. All responses should strive for clarity and depth in explanation.
239
+ ''')
 
 
 
 
240
 
241
+ # Define the assistant node function
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  def assistant(state: MessagesState) -> MessagesState:
243
  return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"][-10:])]}
244
 
245
+ # Build the LangGraph state graph
246
  builder: StateGraph = StateGraph(MessagesState)
 
 
247
  builder.add_node("assistant", assistant)
248
  builder.add_node("tools", ToolNode(tools))
 
 
249
  builder.add_edge(START, "assistant")
250
+ builder.add_conditional_edges("assistant", tools_condition)
 
 
 
 
 
251
  builder.add_edge("tools", "assistant")
252
  memory: MemorySaver = MemorySaver()
 
253
  react_graph_memory: CompiledStateGraph = builder.compile(checkpointer=memory)
254
 
255
+ # Streamlit App Layout
256
  st.title("🚀 Space Agentic Chatbot")
257
+ st.caption("Your AI-powered space exploration assistant providing detailed and informative insights.")
258
 
259
  if "messages" not in st.session_state:
260
  st.session_state.messages = []
 
262
  # Display existing chat messages
263
  for message in st.session_state.messages:
264
  with st.chat_message(message["role"]):
265
+ # If the message content is a URL (likely an image), display it
266
  if message["content"].startswith("http"):
267
  st.image(message["content"])
268
  else:
269
  st.write(message["content"])
270
 
271
  # User input via chat
272
+ if user_input := st.chat_input("Ask about space news, ISS location, astronomy images, or educational content"):
273
  st.session_state.messages.append({"role": "user", "content": user_input})
274
+ with st.chat_message("user"):
275
+ st.write(user_input)
276
  try:
277
+ # Prepare the message for the model and invoke the state graph
278
  messages = [HumanMessage(content=user_input)]
 
 
279
  response = react_graph_memory.invoke({"messages": messages}, config={"configurable": {"thread_id": "1"}})
280
  assistant_response = response["messages"][-1].content
281
 
282
+ # Check for image URL markers in a case-insensitive way
283
+ image_url = None
284
  if "image url:" in assistant_response.lower():
285
+ # Use regex to extract the first URL after "image url:"
286
+ match = re.search(r"[Ii]mage [Uu][Rr][Ll]:\s*(\S+)", assistant_response)
287
+ if match:
288
+ image_url = match.group(1).strip()
 
 
 
 
 
 
 
 
 
 
289
 
290
+ # Save and display the assistant's response
291
+ st.session_state.messages.append({"role": "assistant", "content": assistant_response})
292
+ with st.chat_message("assistant"):
293
+ if image_url:
294
+ st.image(image_url) # Display the image
295
+ st.write(assistant_response)
296
  except Exception as e:
297
  error_msg = f"Error processing request: {str(e)}"
 
298
  st.session_state.messages.append({"role": "assistant", "content": error_msg})
299
  with st.chat_message("assistant"):
300
  st.write(error_msg)