SHAMIL SHAHBAZ AWAN commited on
Commit
7ed5c62
·
verified ·
1 Parent(s): e0b02e9

Update app.py

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