|
import streamlit as st |
|
import os |
|
from openai import OpenAI |
|
import tempfile |
|
from langchain.chains import ConversationalRetrievalChain |
|
from langchain_openai import ChatOpenAI, OpenAIEmbeddings |
|
from langchain.text_splitter import RecursiveCharacterTextSplitter |
|
from langchain_community.vectorstores import Chroma |
|
from langchain_community.document_loaders import ( |
|
PyPDFLoader, |
|
TextLoader, |
|
CSVLoader |
|
) |
|
from datetime import datetime |
|
from pydub import AudioSegment |
|
import pytz |
|
|
|
from langchain.embeddings import SentenceTransformerEmbeddings |
|
from langchain.chains import ConversationalRetrievalChain |
|
from langchain.text_splitter import RecursiveCharacterTextSplitter |
|
from langchain_openai import ChatOpenAI, OpenAIEmbeddings |
|
from langchain_community.vectorstores import Chroma |
|
from langchain_community.document_loaders import PyPDFLoader, TextLoader, CSVLoader |
|
import os |
|
import tempfile |
|
from datetime import datetime |
|
import pytz |
|
|
|
|
|
class DocumentRAG: |
|
def __init__(self): |
|
self.document_store = None |
|
self.qa_chain = None |
|
self.document_summary = "" |
|
self.chat_history = [] |
|
self.last_processed_time = None |
|
self.api_key = os.getenv("OPENAI_API_KEY") |
|
self.init_time = datetime.now(pytz.UTC) |
|
|
|
if not self.api_key: |
|
raise ValueError("API Key not found. Make sure to set the 'OPENAI_API_KEY' environment variable.") |
|
|
|
|
|
self.chroma_persist_dir = "./chroma_storage" |
|
os.makedirs(self.chroma_persist_dir, exist_ok=True) |
|
|
|
def process_documents(self, uploaded_files, embedding_choice): |
|
"""Process uploaded files by saving them temporarily and extracting content.""" |
|
if not self.api_key: |
|
return "Please set the OpenAI API key in the environment variables." |
|
if not uploaded_files: |
|
return "Please upload documents first." |
|
|
|
try: |
|
documents = [] |
|
for uploaded_file in uploaded_files: |
|
|
|
temp_file_path = tempfile.NamedTemporaryFile( |
|
delete=False, suffix=os.path.splitext(uploaded_file.name)[1] |
|
).name |
|
with open(temp_file_path, "wb") as temp_file: |
|
temp_file.write(uploaded_file.read()) |
|
|
|
|
|
if temp_file_path.endswith('.pdf'): |
|
loader = PyPDFLoader(temp_file_path) |
|
elif temp_file_path.endswith('.txt'): |
|
loader = TextLoader(temp_file_path) |
|
elif temp_file_path.endswith('.csv'): |
|
loader = CSVLoader(temp_file_path) |
|
else: |
|
return f"Unsupported file type: {uploaded_file.name}" |
|
|
|
|
|
try: |
|
documents.extend(loader.load()) |
|
except Exception as e: |
|
return f"Error loading {uploaded_file.name}: {str(e)}" |
|
|
|
if not documents: |
|
return "No valid documents were processed. Please check your files." |
|
|
|
|
|
text_splitter = RecursiveCharacterTextSplitter( |
|
chunk_size=1000, |
|
chunk_overlap=200, |
|
length_function=len |
|
) |
|
documents = text_splitter.split_documents(documents) |
|
|
|
|
|
self.document_text = " ".join([doc.page_content for doc in documents]) |
|
|
|
|
|
embeddings = OpenAIEmbeddings(api_key=self.api_key) |
|
|
|
|
|
if embedding_choice == "OpenAI Embeddings": |
|
embeddings = OpenAIEmbeddings(api_key=self.api_key) |
|
else: |
|
embeddings = SentenceTransformerEmbeddings(model_name="NeuML/pubmedbert-base-embeddings") |
|
|
|
|
|
|
|
self.document_store = Chroma.from_documents( |
|
documents, |
|
embeddings, |
|
persist_directory=self.chroma_persist_dir |
|
) |
|
|
|
self.qa_chain = ConversationalRetrievalChain.from_llm( |
|
ChatOpenAI(temperature=0, model_name='gpt-4', api_key=self.api_key), |
|
self.document_store.as_retriever(search_kwargs={'k': 6}), |
|
return_source_documents=True, |
|
verbose=False |
|
) |
|
|
|
self.last_processed_time = datetime.now(pytz.UTC) |
|
return "Documents processed successfully!" |
|
except Exception as e: |
|
return f"Error processing documents: {str(e)}" |
|
|
|
def generate_summary(self, text, language): |
|
"""Generate a clinically relevant summary in the specified language.""" |
|
if not self.api_key: |
|
return "API Key not set. Please set it in the environment variables." |
|
try: |
|
client = OpenAI(api_key=self.api_key) |
|
response = client.chat.completions.create( |
|
model="gpt-4", |
|
messages=[ |
|
{ |
|
"role": "system", |
|
"content": f""" |
|
You are a multilingual clinical AI assistant. Summarize the following medical document (e.g., discharge summary, progress note, or diagnostic report) in **{language}**, preserving all **critical medical information**. |
|
|
|
Please ensure the summary includes: |
|
- Patient history (if available) |
|
- Current diagnosis and relevant symptoms |
|
- Medications and treatments administered |
|
- Investigations and results (if mentioned) |
|
- Any follow-up instructions or discharge plans |
|
|
|
Use clear, concise language suitable for healthcare professionals. Maintain clinical accuracy and do not hallucinate. Aim for a structured summary under 300 words. |
|
""" |
|
}, |
|
{ |
|
"role": "user", |
|
"content": text[4000] |
|
} |
|
], |
|
temperature=0.3 |
|
) |
|
return response.choices[0].message.content |
|
except Exception as e: |
|
return f"Error generating summary: {str(e)}" |
|
|
|
|
|
|
|
def handle_query(self, question, history, language): |
|
"""Handle user queries in the specified language.""" |
|
if not self.qa_chain: |
|
return history + [("System", "Please process the documents first.")] |
|
try: |
|
preface = ( |
|
f"Instruction: Respond in {language}. Be professional and concise, " |
|
f"keeping the response under 300 words. If you cannot provide an answer, say: " |
|
f'"I am not sure about this question. Please try asking something else."' |
|
) |
|
query = f"{preface}\nQuery: {question}" |
|
|
|
result = self.qa_chain({ |
|
"question": query, |
|
"chat_history": [(q, a) for q, a in history] |
|
}) |
|
|
|
if "answer" not in result: |
|
return history + [("System", "Sorry, an error occurred.")] |
|
|
|
history.append((question, result["answer"])) |
|
return history |
|
except Exception as e: |
|
return history + [("System", f"Error: {str(e)}")] |
|
|
|
|
|
if "rag_system" not in st.session_state: |
|
st.session_state.rag_system = DocumentRAG() |
|
|
|
with st.sidebar: |
|
st.markdown("## About:") |
|
st.markdown( |
|
""" |
|
This prototype is part of a research project – **Multilingual Clinical Text Understanding**. |
|
|
|
**Interim Goals:** |
|
1. Summarize clinical notes in local languages |
|
2. Enable question answering over clinical documents using RAG |
|
3. Evaluate performance in under-resourced languages like Bangla, |
|
|
|
**Tasks Covered:** |
|
1. Summarization |
|
2. Question Answering |
|
""" |
|
) |
|
|
|
st.markdown("## Steps:") |
|
st.markdown( |
|
""" |
|
1. Upload documents |
|
2. Generate summary |
|
3. Ask Questions |
|
4. Log User Interactions |
|
|
|
""" |
|
) |
|
|
|
st.markdown("## Contributors:") |
|
st.markdown("Azmine Toushik Wasi, Drishti, Prahitha, Anik, Ashay, AbdurRahman, Iqramul") |
|
|
|
st.markdown("### References:") |
|
st.markdown("[1. RAG_HW HuggingFace Space](https://huggingface.co/spaces/wint543/RAG_HW)") |
|
|
|
|
|
|
|
st.title("Multilingual Clinical Summarization & QA with RAG") |
|
st.image("./cover_image.png", use_container_width=True) |
|
|
|
|
|
|
|
st.subheader("Step 1: Upload and Process Documents") |
|
uploaded_files = st.file_uploader("Upload files (PDF, TXT, CSV)", accept_multiple_files=True) |
|
embedding_model_choice = st.radio( |
|
"Choose Embedding Model:", |
|
["OpenAI Embeddings", "PubMedBERT Embeddings"], |
|
horizontal=True, |
|
key="embedding_model_choice" |
|
) |
|
|
|
|
|
if st.button("Process Documents"): |
|
if uploaded_files: |
|
with st.spinner("Processing documents, please wait..."): |
|
result = st.session_state.rag_system.process_documents(uploaded_files, embedding_model_choice) |
|
|
|
if "successfully" in result: |
|
st.success(result) |
|
else: |
|
st.error(result) |
|
else: |
|
st.warning("No files uploaded.") |
|
|
|
|
|
st.subheader("Step 2: Generate Summary") |
|
st.write("Select Summary Language:") |
|
summary_language_options = ["English", "Hindi", "Bangla", "Spanish", "French", "German", "Chinese", "Japanese"] |
|
summary_language = st.radio( |
|
"", |
|
summary_language_options, |
|
horizontal=True, |
|
key="summary_language" |
|
) |
|
|
|
if st.button("Generate Summary"): |
|
if hasattr(st.session_state.rag_system, "document_text") and st.session_state.rag_system.document_text: |
|
with st.spinner("Generating summary, please wait..."): |
|
summary = st.session_state.rag_system.generate_summary(st.session_state.rag_system.document_text, summary_language) |
|
if summary: |
|
st.session_state.rag_system.document_summary = summary |
|
st.text_area("Document Summary", summary, height=200) |
|
st.success("Summary generated successfully!") |
|
else: |
|
st.error("Failed to generate summary.") |
|
else: |
|
st.info("Please process documents first to generate summary.") |
|
|
|
|
|
st.subheader("Step 3: Ask Questions") |
|
st.write("Select Q&A Language:") |
|
qa_language_options = ["English", "Hindi", "Bangla", "Spanish", "French", "German", "Chinese", "Japanese"] |
|
qa_language = st.radio( |
|
"", |
|
qa_language_options, |
|
horizontal=True, |
|
key="qa_language" |
|
) |
|
|
|
if st.session_state.rag_system.qa_chain: |
|
history = [] |
|
user_question = st.text_input("Ask a question:") |
|
if st.button("Submit Question"): |
|
with st.spinner("Answering your question, please wait..."): |
|
history = st.session_state.rag_system.handle_query(user_question, history, qa_language) |
|
for question, answer in history: |
|
st.chat_message("user").write(question) |
|
st.chat_message("assistant").write(answer) |
|
else: |
|
st.info("Please process documents first to enable Q&A.") |
|
|