vprzybylo commited on
Commit
5135378
·
1 Parent(s): c54d943

Remove app.py and create an empty __init__.py file in the app directory

Browse files
Files changed (3) hide show
  1. app.py +270 -5
  2. app/__init__.py +0 -0
  3. app/src/ui/app.py +0 -279
app.py CHANGED
@@ -1,11 +1,276 @@
 
 
1
  import sys
2
  from pathlib import Path
 
3
 
4
- # Add src directory to Python path
5
- src_path = Path(__file__).parent / "src"
6
- sys.path.append(str(src_path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- from ui.app import main
9
 
10
  if __name__ == "__main__":
11
- main()
 
1
+ import logging
2
+ import os
3
  import sys
4
  from pathlib import Path
5
+ from typing import Annotated, TypedDict
6
 
7
+ import requests
8
+ import streamlit as st
9
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
10
+ from langchain_core.prompts import (
11
+ ChatPromptTemplate,
12
+ HumanMessagePromptTemplate,
13
+ MessagesPlaceholder,
14
+ SystemMessagePromptTemplate,
15
+ )
16
+ from langchain_core.tools import Tool
17
+ from langchain_openai import ChatOpenAI
18
+ from langgraph.graph.message import add_messages
19
+
20
+ sys.path.append(str(Path(__file__).parent))
21
+
22
+ from dotenv import load_dotenv
23
+
24
+ # Now import from app.src
25
+ from app.src.embedding.model import EmbeddingModel
26
+ from app.src.rag.chain import RAGChain
27
+ from app.src.rag.document_loader import GridCodeLoader
28
+ from app.src.rag.vectorstore import VectorStore
29
+
30
+ # Load .env file from base directory
31
+ load_dotenv(Path(__file__).parent / ".env")
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def get_secrets():
36
+ """Get secrets from environment variables."""
37
+ # Skip trying Streamlit secrets and go straight to environment variables
38
+ return {
39
+ "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"),
40
+ "LANGCHAIN_API_KEY": os.getenv("LANGCHAIN_API_KEY"),
41
+ "LANGCHAIN_PROJECT": os.getenv("LANGCHAIN_PROJECT", "GridGuide"),
42
+ "LANGCHAIN_TRACING_V2": os.getenv("LANGCHAIN_TRACING_V2", "true"),
43
+ }
44
+
45
+
46
+ # Set up environment variables from secrets
47
+ secrets = get_secrets()
48
+ for key, value in secrets.items():
49
+ if value:
50
+ os.environ[key] = value
51
+
52
+ # Verify API keys without showing warning
53
+ if not os.getenv("OPENAI_API_KEY"):
54
+ st.error("OpenAI API key not found. Please check your .env file.")
55
+ st.stop()
56
+
57
+
58
+ class WeatherTool:
59
+ def __init__(self):
60
+ self.base_url = "https://api.weather.gov"
61
+ self.headers = {
62
+ "User-Agent": "(Grid Code Assistant, [email protected])",
63
+ "Accept": "application/json",
64
+ }
65
+
66
+ def get_coordinates_from_zip(self, zipcode):
67
+ response = requests.get(f"https://api.zippopotam.us/us/{zipcode}")
68
+ if response.status_code == 200:
69
+ data = response.json()
70
+ return {
71
+ "lat": data["places"][0]["latitude"],
72
+ "lon": data["places"][0]["longitude"],
73
+ "place": data["places"][0]["place name"],
74
+ "state": data["places"][0]["state"],
75
+ }
76
+ return None
77
+
78
+ def run(self, zipcode):
79
+ coords = self.get_coordinates_from_zip(zipcode)
80
+ if not coords:
81
+ return {"error": "Invalid ZIP code or unable to get coordinates."}
82
+
83
+ point_url = f"{self.base_url}/points/{coords['lat']},{coords['lon']}"
84
+ response = requests.get(point_url, headers=self.headers)
85
+
86
+ if response.status_code != 200:
87
+ return {"error": "Unable to fetch weather data."}
88
+
89
+ grid_data = response.json()
90
+ forecast_url = grid_data["properties"]["forecast"]
91
+
92
+ response = requests.get(forecast_url, headers=self.headers)
93
+ if response.status_code == 200:
94
+ forecast_data = response.json()["properties"]["periods"]
95
+ weather_data = {
96
+ "type": "weather",
97
+ "location": f"{coords['place']}, {coords['state']}",
98
+ "current": forecast_data[0],
99
+ "forecast": forecast_data[1:4],
100
+ }
101
+ # Save to session state
102
+ st.session_state.weather_data = weather_data
103
+ return weather_data
104
+ return {"error": "Unable to fetch forecast data."}
105
+
106
+
107
+ def initialize_rag():
108
+ """Initialize RAG system."""
109
+ if "rag_chain" in st.session_state:
110
+ logger.info("Using cached RAG chain from session state")
111
+ return st.session_state.rag_chain
112
+
113
+ data_path = "app/data/raw/grid_code.pdf"
114
+ if not os.path.exists(data_path):
115
+ raise FileNotFoundError(f"PDF not found: {data_path}")
116
+
117
+ with st.spinner("Loading Grid Code documents..."):
118
+ loader = GridCodeLoader(str(data_path), pages=17)
119
+ documents = loader.load_and_split()
120
+ logger.info(f"Loaded {len(documents)} document chunks")
121
+
122
+ with st.spinner("Creating vector store..."):
123
+ embedding_model = EmbeddingModel()
124
+ vectorstore = VectorStore(embedding_model)
125
+ vectorstore = vectorstore.create_vectorstore(documents)
126
+ logger.info("Vector store created successfully")
127
+
128
+ # Cache the RAG chain in session state
129
+ rag_chain = RAGChain(vectorstore)
130
+ st.session_state.rag_chain = rag_chain
131
+ return rag_chain
132
+
133
+
134
+ class RAGTool:
135
+ def __init__(self, rag_chain):
136
+ self.rag_chain = rag_chain
137
+
138
+ def run(self, question: str) -> str:
139
+ """Answer questions using the Grid Code."""
140
+ response = self.rag_chain.invoke(question)
141
+ return response["answer"]
142
+
143
+
144
+ class AgentState(TypedDict):
145
+ """State definition for the agent."""
146
+
147
+ messages: Annotated[list, add_messages]
148
+
149
+
150
+ def create_agent_workflow(rag_chain, weather_tool):
151
+ """Create an agent that can use both RAG and weather tools."""
152
+
153
+ # Define the tools
154
+ tools = [
155
+ Tool(
156
+ name="grid_code_query",
157
+ description="Answer questions about the Grid Code and electrical regulations",
158
+ func=lambda q: rag_chain.invoke(q)["answer"],
159
+ ),
160
+ Tool(
161
+ name="get_weather",
162
+ description="Get weather forecast for a ZIP code. Input should be a 5-digit ZIP code.",
163
+ func=lambda z: weather_tool.run(z),
164
+ ),
165
+ ]
166
+
167
+ # Initialize the LLM
168
+ llm = ChatOpenAI(model="gpt-4o", temperature=0)
169
+
170
+ # Create the custom prompt
171
+ prompt = ChatPromptTemplate.from_messages(
172
+ [
173
+ SystemMessagePromptTemplate.from_template(
174
+ """You are a helpful assistant that specializes in two areas:
175
+ 1. Answering questions about electrical Grid Code regulations
176
+ 2. Providing weather information for specific locations
177
+
178
+ For weather queries:
179
+ - Extract the ZIP code from the question
180
+ - Use the get_weather tool to fetch the forecast
181
+
182
+ For Grid Code questions:
183
+ - Use the grid_code_query tool to find relevant information
184
+ - If the information isn't in the Grid Code, clearly state that
185
+ - Provide specific references when possible
186
+ """
187
+ ),
188
+ MessagesPlaceholder(variable_name="chat_history", optional=True),
189
+ HumanMessagePromptTemplate.from_template("{input}"),
190
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
191
+ ]
192
+ )
193
+
194
+ # Create the agent
195
+ agent = create_tool_calling_agent(llm, tools, prompt)
196
+
197
+ return AgentExecutor(
198
+ agent=agent,
199
+ tools=tools,
200
+ verbose=True,
201
+ handle_parsing_errors=True,
202
+ )
203
+
204
+
205
+ def display_weather(weather_data):
206
+ """Display weather information in a nice format"""
207
+ if "error" in weather_data:
208
+ st.error(weather_data["error"])
209
+ return
210
+
211
+ if weather_data.get("type") == "weather":
212
+ # Location header
213
+ st.header(f"Weather for {weather_data['location']}")
214
+
215
+ # Current conditions
216
+ current = weather_data["current"]
217
+ st.subheader("Current Conditions")
218
+
219
+ # Use columns for current weather layout
220
+ col1, col2 = st.columns(2)
221
+
222
+ with col1:
223
+ # Temperature display with metric
224
+ st.metric(
225
+ "Temperature", f"{current['temperature']}°{current['temperatureUnit']}"
226
+ )
227
+ # Wind information
228
+ st.info(f"💨 Wind: {current['windSpeed']} {current['windDirection']}")
229
+
230
+ with col2:
231
+ # Current forecast
232
+ st.markdown(f"**🌤️ Conditions:** {current['shortForecast']}")
233
+ st.markdown(f"**📝 Details:** {current['detailedForecast']}")
234
+
235
+ # Extended forecast
236
+ st.subheader("Extended Forecast")
237
+ for period in weather_data["forecast"]:
238
+ with st.expander(f"📅 {period['name']}"):
239
+ st.markdown(
240
+ f"**🌡️ Temperature:** {period['temperature']}°{period['temperatureUnit']}"
241
+ )
242
+ st.markdown(
243
+ f"**💨 Wind:** {period['windSpeed']} {period['windDirection']}"
244
+ )
245
+ st.markdown(f"**🌤️ Forecast:** {period['shortForecast']}")
246
+ st.markdown(f"**📝 Details:** {period['detailedForecast']}")
247
+
248
+
249
+ def main():
250
+ st.title("GridGuide: Field Assistant")
251
+
252
+ # Initialize if not in session state
253
+ if "app" not in st.session_state:
254
+ rag_chain = initialize_rag()
255
+ weather_tool = WeatherTool()
256
+ st.session_state.app = create_agent_workflow(rag_chain, weather_tool)
257
+
258
+ # Create the input box
259
+ user_input = st.text_input("Ask about weather or the Grid Code:")
260
+
261
+ if user_input:
262
+ with st.spinner("Processing your request..."):
263
+ # Invoke the agent executor
264
+ result = st.session_state.app.invoke({"input": user_input})
265
+
266
+ # Check if we have weather data in session state
267
+ if "weather_data" in st.session_state:
268
+ display_weather(st.session_state.weather_data)
269
+ # Clear the weather data after displaying
270
+ del st.session_state.weather_data
271
+ else:
272
+ st.write(result["output"])
273
 
 
274
 
275
  if __name__ == "__main__":
276
+ main()
app/__init__.py ADDED
File without changes
app/src/ui/app.py DELETED
@@ -1,279 +0,0 @@
1
- import logging
2
- import os
3
- import sys
4
- from pathlib import Path
5
- from typing import Annotated, TypedDict
6
-
7
- import requests
8
- import streamlit as st
9
- from langchain.agents import AgentExecutor, create_tool_calling_agent
10
- from langchain_core.prompts import (
11
- ChatPromptTemplate,
12
- HumanMessagePromptTemplate,
13
- MessagesPlaceholder,
14
- SystemMessagePromptTemplate,
15
- )
16
- from langchain_core.tools import Tool
17
- from langchain_openai import ChatOpenAI
18
- from langgraph.graph.message import add_messages
19
-
20
- # Configure logging
21
- logging.basicConfig(level=logging.INFO)
22
- logger = logging.getLogger(__name__)
23
-
24
- # Add src directory to Python path
25
- src_path = Path(__file__).parent.parent
26
- sys.path.append(str(src_path))
27
-
28
-
29
- # Get secrets from Hugging Face Space
30
- def get_secrets():
31
- """Get secrets from Hugging Face Space or local environment."""
32
-
33
- return {
34
- "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"),
35
- "LANGCHAIN_API_KEY": os.getenv("LANGCHAIN_API_KEY"),
36
- "LANGCHAIN_PROJECT": os.getenv("LANGCHAIN_PROJECT", "grid-code-assistant"),
37
- "LANGCHAIN_TRACING_V2": os.getenv("LANGCHAIN_TRACING_V2", "true"),
38
- }
39
-
40
-
41
- # Set up environment variables from secrets
42
- secrets = get_secrets()
43
- for key, value in secrets.items():
44
- if value:
45
- os.environ[key] = value
46
-
47
- # Verify API keys
48
- if not os.environ.get("OPENAI_API_KEY"):
49
- st.error(
50
- "OpenAI API key not found. Please set it in the Hugging Face Space secrets."
51
- )
52
- st.stop()
53
-
54
- from embedding.model import EmbeddingModel
55
- from rag.chain import RAGChain
56
- from rag.document_loader import GridCodeLoader
57
- from rag.vectorstore import VectorStore
58
-
59
-
60
- class WeatherTool:
61
- def __init__(self):
62
- self.base_url = "https://api.weather.gov"
63
- self.headers = {
64
- "User-Agent": "(Grid Code Assistant, [email protected])",
65
- "Accept": "application/json",
66
- }
67
-
68
- def get_coordinates_from_zip(self, zipcode):
69
- response = requests.get(f"https://api.zippopotam.us/us/{zipcode}")
70
- if response.status_code == 200:
71
- data = response.json()
72
- return {
73
- "lat": data["places"][0]["latitude"],
74
- "lon": data["places"][0]["longitude"],
75
- "place": data["places"][0]["place name"],
76
- "state": data["places"][0]["state"],
77
- }
78
- return None
79
-
80
- def run(self, zipcode):
81
- coords = self.get_coordinates_from_zip(zipcode)
82
- if not coords:
83
- return {"error": "Invalid ZIP code or unable to get coordinates."}
84
-
85
- point_url = f"{self.base_url}/points/{coords['lat']},{coords['lon']}"
86
- response = requests.get(point_url, headers=self.headers)
87
-
88
- if response.status_code != 200:
89
- return {"error": "Unable to fetch weather data."}
90
-
91
- grid_data = response.json()
92
- forecast_url = grid_data["properties"]["forecast"]
93
-
94
- response = requests.get(forecast_url, headers=self.headers)
95
- if response.status_code == 200:
96
- forecast_data = response.json()["properties"]["periods"]
97
- weather_data = {
98
- "type": "weather",
99
- "location": f"{coords['place']}, {coords['state']}",
100
- "current": forecast_data[0],
101
- "forecast": forecast_data[1:4],
102
- }
103
- # Save to session state
104
- st.session_state.weather_data = weather_data
105
- return weather_data
106
- return {"error": "Unable to fetch forecast data."}
107
-
108
-
109
- def initialize_rag():
110
- """Initialize RAG system."""
111
- if "rag_chain" in st.session_state:
112
- logger.info("Using cached RAG chain from session state")
113
- return st.session_state.rag_chain
114
-
115
- # Use relative path from src directory
116
- data_path = Path(__file__).parent.parent.parent / "app" / "data" / "raw" / "grid_code.pdf"
117
- if not data_path.exists():
118
- raise FileNotFoundError(f"PDF not found: {data_path}")
119
-
120
- with st.spinner("Loading Grid Code documents..."):
121
- loader = GridCodeLoader(str(data_path), pages=17)
122
- documents = loader.load_and_split()
123
- logger.info(f"Loaded {len(documents)} document chunks")
124
-
125
- with st.spinner("Creating vector store..."):
126
- embedding_model = EmbeddingModel()
127
- vectorstore = VectorStore(embedding_model)
128
- vectorstore = vectorstore.create_vectorstore(documents)
129
- logger.info("Vector store created successfully")
130
-
131
- # Cache the RAG chain in session state
132
- rag_chain = RAGChain(vectorstore)
133
- st.session_state.rag_chain = rag_chain
134
- return rag_chain
135
-
136
-
137
- class RAGTool:
138
- def __init__(self, rag_chain):
139
- self.rag_chain = rag_chain
140
-
141
- def run(self, question: str) -> str:
142
- """Answer questions using the Grid Code."""
143
- response = self.rag_chain.invoke(question)
144
- return response["answer"]
145
-
146
-
147
- class AgentState(TypedDict):
148
- """State definition for the agent."""
149
-
150
- messages: Annotated[list, add_messages]
151
-
152
-
153
- def create_agent_workflow(rag_chain, weather_tool):
154
- """Create an agent that can use both RAG and weather tools."""
155
-
156
- # Define the tools
157
- tools = [
158
- Tool(
159
- name="grid_code_query",
160
- description="Answer questions about the Grid Code and electrical regulations",
161
- func=lambda q: rag_chain.invoke(q)["answer"],
162
- ),
163
- Tool(
164
- name="get_weather",
165
- description="Get weather forecast for a ZIP code. Input should be a 5-digit ZIP code.",
166
- func=lambda z: weather_tool.run(z),
167
- ),
168
- ]
169
-
170
- # Initialize the LLM
171
- llm = ChatOpenAI(model="gpt-4o", temperature=0)
172
-
173
- # Create the custom prompt
174
- prompt = ChatPromptTemplate.from_messages(
175
- [
176
- SystemMessagePromptTemplate.from_template(
177
- """You are a helpful assistant that specializes in two areas:
178
- 1. Answering questions about electrical Grid Code regulations
179
- 2. Providing weather information for specific locations
180
-
181
- For weather queries:
182
- - Extract the ZIP code from the question
183
- - Use the get_weather tool to fetch the forecast
184
-
185
- For Grid Code questions:
186
- - Use the grid_code_query tool to find relevant information
187
- - If the information isn't in the Grid Code, clearly state that
188
- - Provide specific references when possible
189
- """
190
- ),
191
- MessagesPlaceholder(variable_name="chat_history", optional=True),
192
- HumanMessagePromptTemplate.from_template("{input}"),
193
- MessagesPlaceholder(variable_name="agent_scratchpad"),
194
- ]
195
- )
196
-
197
- # Create the agent
198
- agent = create_tool_calling_agent(llm, tools, prompt)
199
-
200
- return AgentExecutor(
201
- agent=agent,
202
- tools=tools,
203
- verbose=True,
204
- handle_parsing_errors=True,
205
- )
206
-
207
-
208
- def display_weather(weather_data):
209
- """Display weather information in a nice format"""
210
- if "error" in weather_data:
211
- st.error(weather_data["error"])
212
- return
213
-
214
- if weather_data.get("type") == "weather":
215
- # Location header
216
- st.header(f"Weather for {weather_data['location']}")
217
-
218
- # Current conditions
219
- current = weather_data["current"]
220
- st.subheader("Current Conditions")
221
-
222
- # Use columns for current weather layout
223
- col1, col2 = st.columns(2)
224
-
225
- with col1:
226
- # Temperature display with metric
227
- st.metric(
228
- "Temperature", f"{current['temperature']}°{current['temperatureUnit']}"
229
- )
230
- # Wind information
231
- st.info(f"💨 Wind: {current['windSpeed']} {current['windDirection']}")
232
-
233
- with col2:
234
- # Current forecast
235
- st.markdown(f"**🌤️ Conditions:** {current['shortForecast']}")
236
- st.markdown(f"**📝 Details:** {current['detailedForecast']}")
237
-
238
- # Extended forecast
239
- st.subheader("Extended Forecast")
240
- for period in weather_data["forecast"]:
241
- with st.expander(f"📅 {period['name']}"):
242
- st.markdown(
243
- f"**🌡️ Temperature:** {period['temperature']}°{period['temperatureUnit']}"
244
- )
245
- st.markdown(
246
- f"**💨 Wind:** {period['windSpeed']} {period['windDirection']}"
247
- )
248
- st.markdown(f"**🌤️ Forecast:** {period['shortForecast']}")
249
- st.markdown(f"**📝 Details:** {period['detailedForecast']}")
250
-
251
-
252
- def main():
253
- st.title("GridGuide: Field Assistant")
254
-
255
- # Initialize if not in session state
256
- if "app" not in st.session_state:
257
- rag_chain = initialize_rag()
258
- weather_tool = WeatherTool()
259
- st.session_state.app = create_agent_workflow(rag_chain, weather_tool)
260
-
261
- # Create the input box
262
- user_input = st.text_input("Ask about weather or the Grid Code:")
263
-
264
- if user_input:
265
- with st.spinner("Processing your request..."):
266
- # Invoke the agent executor
267
- result = st.session_state.app.invoke({"input": user_input})
268
-
269
- # Check if we have weather data in session state
270
- if "weather_data" in st.session_state:
271
- display_weather(st.session_state.weather_data)
272
- # Clear the weather data after displaying
273
- del st.session_state.weather_data
274
- else:
275
- st.write(result["output"])
276
-
277
-
278
- if __name__ == "__main__":
279
- main()