amiguel commited on
Commit
c691703
Β·
verified Β·
1 Parent(s): f2855af

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -133
app.py CHANGED
@@ -1,144 +1,129 @@
1
  import streamlit as st
 
2
  import os
3
- import time
4
- import PyPDF2
5
- from docx import Document
6
- import pandas as pd
7
- from dotenv import load_dotenv
8
- from unsloth import FastLanguageModel
9
- from transformers import AutoTokenizer
10
-
11
- # Load environment variables
12
- load_dotenv()
13
-
14
- # Avatars and bios
15
- USER_AVATAR = "https://raw.githubusercontent.com/achilela/vila_fofoka_analysis/9904d9a0d445ab0488cf7395cb863cce7621d897/USER_AVATAR.png"
16
- BOT_AVATAR = "https://raw.githubusercontent.com/achilela/vila_fofoka_analysis/991f4c6e4e1dc7a8e24876ca5aae5228bcdb4dba/Ataliba_Avatar.jpg"
17
-
18
- ATALIBA_BIO = """
19
- **I am Ataliba Miguel's Digital Twin** πŸ€–
20
-
21
- **Background:**
22
- - πŸŽ“ Mechanical Engineering (BSc)
23
- - β›½ Oil & Gas Engineering (MSc Specialization)
24
- - πŸ”§ 17+ years in Oil & Gas Industry
25
- - πŸ” Current: Topside Inspection Methods Engineer @ TotalEnergies
26
- - πŸ€– AI Practitioner Specialist
27
- - πŸš€ Founder of ValonyLabs (AI solutions for industrial corrosion, retail analytics, and KPI monitoring)
28
-
29
- **Capabilities:**
30
- - Technical document analysis
31
- - Engineering insights
32
- - AI-powered problem solving
33
- - Cross-domain knowledge integration
34
-
35
- Ask me about engineering challenges, AI applications, or industry best practices!
36
- """
37
-
38
- # UI Setup
39
- st.markdown("""
40
- <style>
41
- @import url('https://fonts.cdnfonts.com/css/tw-cen-mt');
42
- * { font-family: 'Tw Cen MT', sans-serif; }
43
- .st-emotion-cache-1y4p8pa { padding: 2rem 1rem; }
44
- </style>
45
- """, unsafe_allow_html=True)
46
-
47
- st.title("πŸš€ Ataliba o Agent Nerdx πŸš€")
48
-
49
- # Sidebar
50
  with st.sidebar:
51
- st.header("⚑️ Hugging Face Model Loaded")
52
- st.markdown("Model: `amiguel/unsloth_finetune_test` with LoRA")
53
- uploaded_file = st.file_uploader("Upload technical documents", type=["pdf", "docx", "xlsx", "xlsm"])
54
-
55
- # Session state
56
- if "file_context" not in st.session_state:
57
- st.session_state.file_context = None
58
- if "chat_history" not in st.session_state:
59
- st.session_state.chat_history = []
60
-
61
- # File parser
62
- def parse_file(file):
63
- try:
64
- if file.type == "application/pdf":
65
- reader = PyPDF2.PdfReader(file)
66
- return "\n".join([page.extract_text() for page in reader.pages])
67
- elif file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
68
- doc = Document(file)
69
- return "\n".join([para.text for para in doc.paragraphs])
70
- elif file.type in ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel"]:
71
- df = pd.read_excel(file)
72
- return df.to_string()
73
- except Exception as e:
74
- st.error(f"Error processing file: {str(e)}")
75
- return None
76
-
77
- # Process file
78
- if uploaded_file and not st.session_state.file_context:
79
- st.session_state.file_context = parse_file(uploaded_file)
80
- if st.session_state.file_context:
81
- st.sidebar.success("βœ… Document loaded successfully")
82
-
83
- # Load model
84
  @st.cache_resource
85
- def load_unsloth_model():
86
- base_model = "unsloth/llama-3-8b-Instruct-bnb-4bit"
87
- adapter = "amiguel/unsloth_finetune_test"
88
- model, tokenizer = FastLanguageModel.from_pretrained(
89
- model_name=base_model,
90
- max_seq_length=2048,
91
- dtype=None,
92
- load_in_4bit=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  )
94
- model.load_adapter(adapter)
95
- FastLanguageModel.for_inference(model)
96
- return model, tokenizer
97
-
98
- # Generate response
99
- def generate_response(prompt):
100
- bio_triggers = ['who are you', 'ataliba', 'yourself', 'skilled at',
101
- 'background', 'experience', 'valonylabs', 'totalenergies']
102
-
103
- if any(trigger in prompt.lower() for trigger in bio_triggers):
104
- for line in ATALIBA_BIO.split('\n'):
105
- yield line + '\n'
106
- time.sleep(0.1)
107
- return
108
-
109
- try:
110
- model, tokenizer = load_unsloth_model()
111
- context = st.session_state.file_context or ""
112
- full_prompt = f"You are an expert in life balance and general knowledge. Use the context to answer precisely.\nContext: {context}\n\nQuestion: {prompt}"
113
-
114
- inputs = tokenizer(full_prompt, return_tensors="pt").to(model.device)
115
- outputs = model.generate(**inputs, max_new_tokens=256, do_sample=False)
116
- response = tokenizer.decode(outputs[0], skip_special_tokens=True)
117
-
118
- for line in response.split('\n'):
119
- yield line + '\n'
120
- time.sleep(0.05)
121
-
122
- except Exception as e:
123
- yield f"⚠️ Model Error: {str(e)}"
124
-
125
- # Chat interface
126
- for msg in st.session_state.chat_history:
127
  with st.chat_message(msg["role"], avatar=USER_AVATAR if msg["role"] == "user" else BOT_AVATAR):
128
  st.markdown(msg["content"])
129
 
130
- if prompt := st.chat_input("Ask about documents or technical matters..."):
131
- st.session_state.chat_history.append({"role": "user", "content": prompt})
132
- with st.chat_message("user", avatar=USER_AVATAR):
133
- st.markdown(prompt)
134
 
135
- with st.chat_message("assistant", avatar=BOT_AVATAR):
136
- response_placeholder = st.empty()
137
- full_response = ""
 
138
 
139
- for chunk in generate_response(prompt):
140
- full_response += chunk
141
- response_placeholder.markdown(full_response + "β–Œ")
142
 
143
- response_placeholder.markdown(full_response)
144
- st.session_state.chat_history.append({"role": "assistant", "content": full_response})
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import torch
3
  import os
4
+ import tempfile
5
+ from threading import Thread
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
7
+ from langchain_community.document_loaders import PyPDFLoader, TextLoader
8
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
9
+ from langchain_community.embeddings import HuggingFaceEmbeddings
10
+ from langchain.vectorstores import FAISS
11
+ from langchain.retrievers import BM25Retriever, EnsembleRetriever
12
+ from langchain.schema import Document
13
+ from langchain.docstore.document import Document as LangchainDocument
14
+
15
+ # --- Avatars ---
16
+ USER_AVATAR = "πŸ‘€"
17
+ BOT_AVATAR = "πŸ€–"
18
+
19
+ # --- HF Token ---
20
+ HF_TOKEN = st.secrets["HF_TOKEN"]
21
+
22
+ # --- Page Config ---
23
+ st.set_page_config(page_title="Hybrid RAG with Streaming", page_icon="πŸ“„", layout="centered")
24
+ st.title("πŸ“„ Hybrid Search + Streaming Chat")
25
+
26
+ # --- Sidebar Upload ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  with st.sidebar:
28
+ st.header("πŸ“€ Upload Documents")
29
+ uploaded_files = st.file_uploader("Upload PDFs or .txt files", type=["pdf", "txt"], accept_multiple_files=True)
30
+ clear_chat = st.button("🧹 Clear Conversation")
31
+
32
+ # --- Session State ---
33
+ if "messages" not in st.session_state or clear_chat:
34
+ st.session_state.messages = []
35
+
36
+ # --- Load LLM ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  @st.cache_resource
38
+ def load_model():
39
+ model_id = "tiiuae/falcon-7b-instruct"
40
+ tokenizer = AutoTokenizer.from_pretrained(model_id, token=HF_TOKEN)
41
+ model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float16, device_map="auto", token=HF_TOKEN)
42
+ return tokenizer, model
43
+
44
+ tokenizer, model = load_model()
45
+
46
+ # --- Load & Chunk Documents ---
47
+ def process_documents(files):
48
+ documents = []
49
+ for file in files:
50
+ suffix = ".pdf" if file.name.endswith(".pdf") else ".txt"
51
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_file:
52
+ tmp_file.write(file.read())
53
+ tmp_file_path = tmp_file.name
54
+ loader = PyPDFLoader(tmp_file_path) if suffix == ".pdf" else TextLoader(tmp_file_path)
55
+ documents.extend(loader.load())
56
+ return documents
57
+
58
+ def chunk_documents(documents):
59
+ splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
60
+ return splitter.split_documents(documents)
61
+
62
+ def build_hybrid_retriever(chunks):
63
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
64
+ faiss_store = FAISS.from_documents(chunks, embeddings)
65
+ faiss_retriever = faiss_store.as_retriever(search_type="similarity", search_kwargs={"k": 5})
66
+
67
+ bm25_retriever = BM25Retriever.from_documents([LangchainDocument(page_content=d.page_content) for d in chunks])
68
+ bm25_retriever.k = 5
69
+
70
+ hybrid = EnsembleRetriever(retrievers=[faiss_retriever, bm25_retriever], weights=[0.5, 0.5])
71
+ return hybrid
72
+
73
+ # --- Prompt Construction ---
74
+ def build_prompt(history, context=""):
75
+ prompt = (
76
+ "You are DigiTwin, an expert in reliability, inspection, and maintenance of piping, structures, vessels, and topside assets.\n"
77
+ f"Use the following context to help answer questions:\n\n{context}\n\n"
78
  )
79
+ for turn in history:
80
+ role = "User" if turn["role"] == "user" else "Assistant"
81
+ prompt += f"{role}: {turn['content']}\n"
82
+ prompt += "Assistant:"
83
+ return prompt
84
+
85
+ # --- Generator for Streaming ---
86
+ def generate_streaming_response(prompt):
87
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
88
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
89
+ thread = Thread(target=model.generate, kwargs={**inputs, "streamer": streamer, "max_new_tokens": 300})
90
+ thread.start()
91
+ output = ""
92
+ for token in streamer:
93
+ output += token
94
+ yield output
95
+
96
+ # --- Run Document Processing and Retrieval ---
97
+ retriever = None
98
+ if uploaded_files:
99
+ with st.spinner("πŸ“š Processing documents..."):
100
+ docs = process_documents(uploaded_files)
101
+ chunks = chunk_documents(docs)
102
+ retriever = build_hybrid_retriever(chunks)
103
+ st.success("βœ… Document processing complete.")
104
+
105
+ # --- Display Past Messages ---
106
+ for msg in st.session_state.messages:
 
 
 
 
 
107
  with st.chat_message(msg["role"], avatar=USER_AVATAR if msg["role"] == "user" else BOT_AVATAR):
108
  st.markdown(msg["content"])
109
 
110
+ # --- Main Chat Input ---
111
+ if prompt := st.chat_input("Ask a question..."):
112
+ st.chat_message("user", avatar=USER_AVATAR).markdown(prompt)
113
+ st.session_state.messages.append({"role": "user", "content": prompt})
114
 
115
+ context = ""
116
+ if retriever:
117
+ docs = retriever.get_relevant_documents(prompt)
118
+ context = "\n\n".join([doc.page_content for doc in docs])
119
 
120
+ full_prompt = build_prompt(st.session_state.messages, context=context)
 
 
121
 
122
+ with st.chat_message("assistant", avatar=BOT_AVATAR):
123
+ response_container = st.empty()
124
+ answer = ""
125
+ for chunk in generate_streaming_response(full_prompt):
126
+ answer = chunk
127
+ response_container.markdown(answer + "β–Œ", unsafe_allow_html=True)
128
+ response_container.markdown(answer)
129
+ st.session_state.messages.append({"role": "assistant", "content": answer})