imkrish commited on
Commit
39f26bf
·
1 Parent(s): 84ddf53

Fix ASGI app error and stabilize Streamlit with top-level api_app

Browse files
Files changed (2) hide show
  1. README.md +5 -3
  2. app.py +82 -118
README.md CHANGED
@@ -11,12 +11,12 @@ pinned: false
11
 
12
  # RAG-Based Question Answering System
13
 
14
- This system uses **FastAPI** for its API and **Gradio** for its user interface, running on **ZeroGPU**.
15
 
16
  ## 🚀 How it works
17
 
18
- 1. **Upload**: Send PDF or TXT files.
19
- 2. **Retrieve**: FAISS finds the most relevant chunks.
20
  3. **Generate**: Gemini 1.5 Flash synthesizes an answer using **ZeroGPU**.
21
 
22
  ## 🛠️ API Access
@@ -26,4 +26,6 @@ The FastAPI endpoints are available at:
26
  - `POST /upload`: Upload documents.
27
  - `POST /ask`: Query the documents.
28
 
 
 
29
  Make sure to set `GOOGLE_API_KEY` in your Space Secrets.
 
11
 
12
  # RAG-Based Question Answering System
13
 
14
+ This system uses **Streamlit** for its user interface and **FastAPI** for its background API, running on **ZeroGPU**.
15
 
16
  ## 🚀 How it works
17
 
18
+ 1. **Upload**: Use the sidebar to upload PDF or TXT files.
19
+ 2. **Retrieve**: FAISS finds the most relevant chunks from your documents.
20
  3. **Generate**: Gemini 1.5 Flash synthesizes an answer using **ZeroGPU**.
21
 
22
  ## 🛠️ API Access
 
26
  - `POST /upload`: Upload documents.
27
  - `POST /ask`: Query the documents.
28
 
29
+ _Note: The API runs on a background thread within the Streamlit process._
30
+
31
  Make sure to set `GOOGLE_API_KEY` in your Space Secrets.
app.py CHANGED
@@ -16,156 +16,120 @@ from app.services.document_processor import DocumentProcessor
16
  from app.services.vector_store import vector_store
17
  from app.services.llm_service import llm_service
18
 
19
- # --- API THREAD ---
20
- def run_api():
21
- api_app = FastAPI(title="RAG API")
22
- api_app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
23
-
24
- @api_app.post("/upload")
25
- async def api_upload(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
26
- file_id = str(uuid.uuid4())
27
- temp_path = os.path.join(settings.UPLOADS_DIR, f"{file_id}_{file.filename}")
28
- os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
29
- with open(temp_path, "wb") as buffer:
30
- shutil.copyfileobj(file.file, buffer)
31
- def process_task(path, name):
32
- text = DocumentProcessor.extract_text(path)
33
- chunks = DocumentProcessor.chunk_text(text)
34
- vector_store.add_documents(chunks, name)
35
- if os.path.exists(path): os.remove(path)
36
- background_tasks.add_task(process_task, temp_path, file.filename)
37
- return {"message": "Success", "filename": file.filename}
38
-
39
- @api_app.post("/ask")
40
- async def api_ask(request: Request):
41
- data = await request.json()
42
- question = data.get("question")
43
- relevant_chunks = vector_store.search(question)
44
- if not relevant_chunks: return {"answer": "No info", "sources": []}
45
- # In API context, ZeroGPU might be tricky from thread,
46
- # but for internal use it should work if called correctly.
47
- answer = llm_service.generate_answer(question, relevant_chunks)
48
- sources = list(set([c['source'] for c in relevant_chunks]))
49
- return {"answer": answer, "sources": sources}
50
-
51
  uvicorn.run(api_app, host="0.0.0.0", port=8000)
52
 
53
- # Start API in background if not already running
54
- if "api_thread" not in st.session_state:
55
- thread = threading.Thread(target=run_api, daemon=True)
56
- thread.start()
57
- st.session_state.api_thread = True
58
 
59
- # Page Configuration
60
  st.set_page_config(
61
- page_title="Knowledge Nexus | RAG Assistant",
62
  page_icon="📚",
63
  layout="wide"
64
  )
65
 
66
- # Custom CSS for Premium Look (No Icons)
67
  st.markdown("""
68
- <style>
69
- .main {
70
- background-color: #f8fafc;
71
- }
72
- .stButton>button {
73
- width: 100%;
74
- border-radius: 10px;
75
- height: 3em;
76
- background-color: #4f46e5;
77
- color: white;
78
- border: none;
79
- }
80
- .stButton>button:hover {
81
- background-color: #4338ca;
82
- color: white;
83
- }
84
- .upload-box {
85
- border: 2px dashed #cbd5e1;
86
- padding: 20px;
87
- border-radius: 15px;
88
- text-align: center;
89
- }
90
- .status-text {
91
- color: #64748b;
92
- font-size: 0.9em;
93
- }
94
- /* Hide Streamlit Branding */
95
- #MainMenu {visibility: hidden;}
96
- footer {visibility: hidden;}
97
- header {visibility: hidden;}
98
- </style>
99
- """, unsafe_allow_html=True)
100
-
101
- # Zero-GPU Decorated Function
102
  @spaces.GPU
103
  def get_llm_response(query: str, chunks: List):
104
  return llm_service.generate_answer(query, chunks)
105
 
106
- # Sidebar for Ingestion
107
  with st.sidebar:
108
  st.title("Knowledge Nexus")
109
  st.markdown("---")
110
- st.header("Upload Section")
111
- st.write("Link your PDF or TXT documents to the system brain.")
112
 
113
- uploaded_file = st.file_uploader("Choose a file", type=["pdf", "txt"], label_visibility="collapsed")
114
 
115
- if uploaded_file is not None:
116
- if st.button("Process Document"):
117
- with st.spinner("Analyzing and indexing..."):
118
- # Save temporarily
119
- file_id = str(uuid.uuid4())
120
- temp_path = os.path.join(settings.UPLOADS_DIR, f"{file_id}_{uploaded_file.name}")
121
- os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
122
-
123
- with open(temp_path, "wb") as f:
124
- f.write(uploaded_file.getbuffer())
125
-
126
- try:
127
- text = DocumentProcessor.extract_text(temp_path)
128
- chunks = DocumentProcessor.chunk_text(text)
129
- vector_store.add_documents(chunks, uploaded_file.name)
130
- st.success(f"Successfully linked: {uploaded_file.name}")
131
- except Exception as e:
132
- st.error(f"Error processing: {str(e)}")
133
- finally:
134
- if os.path.exists(temp_path):
135
- os.remove(temp_path)
136
 
137
  st.markdown("---")
138
- st.header("System Status")
139
- st.info("Model: Gemini 1.5 Flash\nRetrieval: FAISS\nCompute: ZeroGPU")
 
140
 
141
- # Main Chat Interface
142
  st.title("Document Interaction")
143
- st.write("Ask questions based on your uploaded knowledge base.")
144
 
145
  if "messages" not in st.session_state:
146
  st.session_state.messages = []
147
 
148
- # Display chat history
149
- for message in st.session_state.messages:
150
- with st.chat_message(message["role"]):
151
- st.markdown(message["content"])
152
 
153
- # User Input
154
- if prompt := st.chat_input("Enter your question..."):
155
  st.session_state.messages.append({"role": "user", "content": prompt})
156
  with st.chat_message("user"):
157
  st.markdown(prompt)
158
 
159
  with st.chat_message("assistant"):
160
- with st.spinner("Searching knowledge base..."):
161
- relevant_chunks = vector_store.search(prompt)
162
-
163
- if not relevant_chunks:
164
- response = "I could not find any relevant information in the linked documents."
165
  else:
166
- response_text = get_llm_response(prompt, relevant_chunks)
167
- sources = list(set([c['source'] for c in relevant_chunks]))
168
- response = f"{response_text}\n\nSources: {', '.join(sources)}"
169
-
170
  st.markdown(response)
171
  st.session_state.messages.append({"role": "assistant", "content": response})
 
16
  from app.services.vector_store import vector_store
17
  from app.services.llm_service import llm_service
18
 
19
+ # --- FASTAPI APP DEFINITION (Exposed at top level) ---
20
+ api_app = FastAPI(title="RAG API")
21
+ api_app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
22
+
23
+ @api_app.post("/upload")
24
+ async def api_upload(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
25
+ file_id = str(uuid.uuid4())
26
+ temp_path = os.path.join(settings.UPLOADS_DIR, f"{file_id}_{file.filename}")
27
+ os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
28
+ with open(temp_path, "wb") as buffer:
29
+ shutil.copyfileobj(file.file, buffer)
30
+ def process_task(path, name):
31
+ text = DocumentProcessor.extract_text(path)
32
+ chunks = DocumentProcessor.chunk_text(text)
33
+ vector_store.add_documents(chunks, name)
34
+ if os.path.exists(path): os.remove(path)
35
+ background_tasks.add_task(process_task, temp_path, file.filename)
36
+ return {"message": "Success", "filename": file.filename}
37
+
38
+ @api_app.post("/ask")
39
+ async def api_ask(request: Request):
40
+ data = await request.json()
41
+ question = data.get("question")
42
+ relevant_chunks = vector_store.search(question)
43
+ if not relevant_chunks: return {"answer": "No info", "sources": []}
44
+ answer = llm_service.generate_answer(question, relevant_chunks)
45
+ sources = list(set([c['source'] for c in relevant_chunks]))
46
+ return {"answer": answer, "sources": sources}
47
+
48
+ # --- BACKGROUND API RUNNER ---
49
+ def run_api_server():
50
+ # Run on a different port than Streamlit (7860)
51
  uvicorn.run(api_app, host="0.0.0.0", port=8000)
52
 
53
+ # Start API thread
54
+ if "api_started" not in st.session_state:
55
+ st.session_state.api_started = True
56
+ threading.Thread(target=run_api_server, daemon=True).start()
 
57
 
58
+ # --- STREAMLIT UI ---
59
  st.set_page_config(
60
+ page_title="Knowledge Nexus",
61
  page_icon="📚",
62
  layout="wide"
63
  )
64
 
65
+ # Premium Text-Only CSS
66
  st.markdown("""
67
+ <style>
68
+ .main { background-color: #f8fafc; }
69
+ .stChatFloatingInputContainer { bottom: 20px; }
70
+ footer { visibility: hidden; }
71
+ header { visibility: hidden; }
72
+ </style>
73
+ """, unsafe_allow_html=True)
74
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  @spaces.GPU
76
  def get_llm_response(query: str, chunks: List):
77
  return llm_service.generate_answer(query, chunks)
78
 
79
+ # Sidebar
80
  with st.sidebar:
81
  st.title("Knowledge Nexus")
82
  st.markdown("---")
83
+ st.header("Document Upload")
84
+ st.write("Upload PDF/TXT to link them to the AI.")
85
 
86
+ uploaded_file = st.file_uploader("Upload", type=["pdf", "txt"], label_visibility="collapsed")
87
 
88
+ if uploaded_file and st.button("Index Document"):
89
+ with st.spinner("Processing..."):
90
+ file_id = str(uuid.uuid4())
91
+ temp_path = os.path.join(settings.UPLOADS_DIR, f"{file_id}_{uploaded_file.name}")
92
+ os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
93
+ with open(temp_path, "wb") as f:
94
+ f.write(uploaded_file.getbuffer())
95
+ try:
96
+ text = DocumentProcessor.extract_text(temp_path)
97
+ chunks = DocumentProcessor.chunk_text(text)
98
+ vector_store.add_documents(chunks, uploaded_file.name)
99
+ st.success(f"Linked: {uploaded_file.name}")
100
+ except Exception as e:
101
+ st.error(f"Error: {str(e)}")
102
+ finally:
103
+ if os.path.exists(temp_path): os.remove(temp_path)
 
 
 
 
 
104
 
105
  st.markdown("---")
106
+ st.write("Model: Gemini 1.5 Flash")
107
+ st.write("Retrieval: FAISS")
108
+ st.write("Compute: ZeroGPU")
109
 
110
+ # Chat UI
111
  st.title("Document Interaction")
 
112
 
113
  if "messages" not in st.session_state:
114
  st.session_state.messages = []
115
 
116
+ for m in st.session_state.messages:
117
+ with st.chat_message(m["role"]):
118
+ st.markdown(m["content"])
 
119
 
120
+ if prompt := st.chat_input("Ask a question about your documents..."):
 
121
  st.session_state.messages.append({"role": "user", "content": prompt})
122
  with st.chat_message("user"):
123
  st.markdown(prompt)
124
 
125
  with st.chat_message("assistant"):
126
+ with st.spinner("Searching..."):
127
+ chunks = vector_store.search(prompt)
128
+ if not chunks:
129
+ response = "No relevant information found."
 
130
  else:
131
+ ans = get_llm_response(prompt, chunks)
132
+ sources = list(set([c['source'] for c in chunks]))
133
+ response = f"{ans}\n\nSources: {', '.join(sources)}"
 
134
  st.markdown(response)
135
  st.session_state.messages.append({"role": "assistant", "content": response})