Upload 8 files
Browse filesUpload the main files.
- .gitattributes +1 -0
- Edu-Researcher.py +126 -0
- Edu-Scraper.py +153 -0
- Hellopage.py +40 -0
- README.md +21 -3
- chromedriver.exe +3 -0
- pdf_rag.py +169 -0
- requirements.txt +11 -0
- webp.png +0 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
chromedriver.exe filter=lfs diff=lfs merge=lfs -text
|
Edu-Researcher.py
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import requests
|
3 |
+
import logging
|
4 |
+
import os
|
5 |
+
from duckduckgo_search import DDGS # Using DuckDuckGo search library
|
6 |
+
from langchain.embeddings import HuggingFaceEmbeddings
|
7 |
+
from langgraph.graph import START, END, StateGraph
|
8 |
+
from typing import Dict, Any
|
9 |
+
|
10 |
+
# Configure logging
|
11 |
+
logging.basicConfig(level=logging.INFO)
|
12 |
+
logger = logging.getLogger(__name__)
|
13 |
+
|
14 |
+
# Initialize session state for messages
|
15 |
+
if "messages" not in st.session_state:
|
16 |
+
st.session_state.messages = []
|
17 |
+
|
18 |
+
# Refresh messages if switching to Edu-Researcher
|
19 |
+
if st.session_state.get("active_function") != "Edu-Researcher":
|
20 |
+
st.session_state.messages = []
|
21 |
+
st.session_state.active_function = "Edu-Researcher"
|
22 |
+
|
23 |
+
# Sidebar configuration
|
24 |
+
with st.sidebar:
|
25 |
+
st.header("Researcher Configuration")
|
26 |
+
st.markdown("[Get HuggingFace Token](https://huggingface.co/settings/tokens)")
|
27 |
+
st.info("Using DuckDuckGo search for web results")
|
28 |
+
system_message = st.text_area(
|
29 |
+
"System Message",
|
30 |
+
value="You are an assistant for research. Use the retrieved web snippets to answer the query concisely.",
|
31 |
+
height=100
|
32 |
+
)
|
33 |
+
max_tokens = st.slider("Max Tokens", 10, 4000, 300)
|
34 |
+
temperature = st.slider("Temperature", 0.1, 4.0, 0.3)
|
35 |
+
top_p = st.slider("Top-p", 0.1, 1.0, 0.6)
|
36 |
+
|
37 |
+
# Set up embedding model (for context retrieval)
|
38 |
+
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
39 |
+
|
40 |
+
st.title("Edu-Researcher")
|
41 |
+
st.caption("Powered by DuckDuckGo Search, LangGraph, and Hugging Face Inference API")
|
42 |
+
|
43 |
+
# Input research query
|
44 |
+
query_input = st.text_input("Enter your research query:")
|
45 |
+
|
46 |
+
# Define our research state as a dictionary
|
47 |
+
# The state will hold: query, sources, snippets, and response
|
48 |
+
ResearchState = Dict[str, Any]
|
49 |
+
|
50 |
+
def search_web_node(state: ResearchState) -> ResearchState:
|
51 |
+
# Use DuckDuckGo to search for the query (max 3 results) using DDGS
|
52 |
+
sources = []
|
53 |
+
snippets = []
|
54 |
+
with DDGS() as ddgs:
|
55 |
+
results = ddgs.text(state["query"], max_results=3)
|
56 |
+
for res in results:
|
57 |
+
sources.append(res.get("href", ""))
|
58 |
+
snippet = res.get("body") or res.get("title", "")
|
59 |
+
snippets.append(snippet)
|
60 |
+
state["sources"] = sources
|
61 |
+
state["snippets"] = snippets
|
62 |
+
return state
|
63 |
+
|
64 |
+
def generate_answer_node(state: ResearchState) -> ResearchState:
|
65 |
+
# Combine retrieved snippets into a context string
|
66 |
+
context = "\n\n".join(state.get("snippets", []))
|
67 |
+
full_prompt = (
|
68 |
+
f"{system_message}\n\n"
|
69 |
+
f"Context: {context}\n\n"
|
70 |
+
f"Query: {state['query']}\n\n"
|
71 |
+
f"Please provide a succinct and complete answer within {max_tokens} tokens."
|
72 |
+
)
|
73 |
+
# Query the Hugging Face API using the selected model endpoint
|
74 |
+
model_endpoint = "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-72B-Instruct"
|
75 |
+
headers = {"Authorization": f"Bearer {st.secrets['HF_TOKEN']}"}
|
76 |
+
logger.info(f"Sending request to {model_endpoint} with prompt: {full_prompt}")
|
77 |
+
response = requests.post(model_endpoint, headers=headers, json={
|
78 |
+
"inputs": full_prompt,
|
79 |
+
"parameters": {
|
80 |
+
"max_new_tokens": max_tokens,
|
81 |
+
"temperature": temperature,
|
82 |
+
"top_p": top_p,
|
83 |
+
"return_full_text": False
|
84 |
+
}
|
85 |
+
})
|
86 |
+
logger.info(f"Received response: {response.status_code}, {response.text}")
|
87 |
+
try:
|
88 |
+
output = response.json()
|
89 |
+
except requests.exceptions.JSONDecodeError:
|
90 |
+
logger.error(f"Failed to decode JSON response: {response.text}")
|
91 |
+
output = None
|
92 |
+
if output and isinstance(output, list) and len(output) > 0 and "generated_text" in output[0]:
|
93 |
+
state["response"] = output[0]["generated_text"].strip()
|
94 |
+
else:
|
95 |
+
state["response"] = "No response generated - please try again."
|
96 |
+
return state
|
97 |
+
|
98 |
+
def render_message(content):
|
99 |
+
# Render as LaTeX if enclosed by $$, else as markdown.
|
100 |
+
if content.strip().startswith("$$") and content.strip().endswith("$$"):
|
101 |
+
st.latex(content.strip()[2:-2])
|
102 |
+
else:
|
103 |
+
st.markdown(content)
|
104 |
+
|
105 |
+
# Build the state graph using langgraph
|
106 |
+
builder = StateGraph(dict)
|
107 |
+
builder.add_node("search_web", search_web_node)
|
108 |
+
builder.add_node("generate_answer", generate_answer_node)
|
109 |
+
builder.add_edge(START, "search_web")
|
110 |
+
builder.add_edge("search_web", "generate_answer")
|
111 |
+
builder.add_edge("generate_answer", END)
|
112 |
+
graph = builder.compile()
|
113 |
+
|
114 |
+
if query_input:
|
115 |
+
# Initialize state with the query
|
116 |
+
initial_state = {"query": query_input}
|
117 |
+
with st.spinner("Searching the web and generating response..."):
|
118 |
+
result_state = graph.invoke(initial_state)
|
119 |
+
if result_state:
|
120 |
+
render_message(result_state["response"])
|
121 |
+
st.subheader("Sources:")
|
122 |
+
for src in result_state.get("sources", []):
|
123 |
+
st.write(src)
|
124 |
+
else:
|
125 |
+
st.error("No response generated.")
|
126 |
+
st.session_state.messages.append({"role": "researcher", "content": result_state.get("response", "No answer.")})
|
Edu-Scraper.py
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import requests
|
3 |
+
import logging
|
4 |
+
import os
|
5 |
+
from langchain_community.document_loaders import SeleniumURLLoader
|
6 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
7 |
+
from langchain_community.vectorstores import InMemoryVectorStore
|
8 |
+
from langchain.embeddings import HuggingFaceEmbeddings
|
9 |
+
|
10 |
+
# Disable proxy for Hugging Face endpoints
|
11 |
+
os.environ["NO_PROXY"] = "huggingface.co"
|
12 |
+
|
13 |
+
# PATCH: Force SeleniumURLLoader to use local chromedriver.exe using Service
|
14 |
+
import langchain_community.document_loaders.url_selenium as url_selenium
|
15 |
+
from selenium import webdriver
|
16 |
+
from selenium.webdriver.chrome.options import Options
|
17 |
+
from selenium.webdriver.chrome.service import Service
|
18 |
+
|
19 |
+
def patched_get_driver(self):
|
20 |
+
chrome_options = Options()
|
21 |
+
chrome_options.add_argument("--headless")
|
22 |
+
# Use a simple path: assume "chromedriver.exe" is in the current working directory
|
23 |
+
service = Service("chromedriver.exe")
|
24 |
+
return webdriver.Chrome(service=service, options=chrome_options)
|
25 |
+
|
26 |
+
url_selenium.SeleniumURLLoader._get_driver = patched_get_driver
|
27 |
+
# END PATCH
|
28 |
+
|
29 |
+
# Configure logging
|
30 |
+
logging.basicConfig(level=logging.INFO)
|
31 |
+
logger = logging.getLogger(__name__)
|
32 |
+
|
33 |
+
# Initialize session state for chat history and vector store
|
34 |
+
if "messages" not in st.session_state:
|
35 |
+
st.session_state.messages = []
|
36 |
+
if "vector_store" not in st.session_state:
|
37 |
+
st.session_state.vector_store = None
|
38 |
+
|
39 |
+
# Set up embedding model (used for indexing)
|
40 |
+
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
41 |
+
|
42 |
+
# Sidebar configuration (similar to pdf_rag.py)
|
43 |
+
with st.sidebar:
|
44 |
+
st.header("Model Configuration")
|
45 |
+
st.markdown("[Get HuggingFace Token](https://huggingface.co/settings/tokens)")
|
46 |
+
# Updated model options list with additional option for Qwen2.5-72B-Instruct
|
47 |
+
model_options = [
|
48 |
+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
|
49 |
+
"Qwen/Qwen2.5-72B-Instruct"
|
50 |
+
]
|
51 |
+
selected_model = st.selectbox("Select Model", model_options, index=0)
|
52 |
+
system_message = st.text_area(
|
53 |
+
"System Message",
|
54 |
+
value="You are an assistant for question-answering tasks created by ruslanmv.com. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum for each question and keep the answer concise.",
|
55 |
+
height=100
|
56 |
+
)
|
57 |
+
max_tokens = st.slider("Max Tokens", 10, 4000, 300)
|
58 |
+
temperature = st.slider("Temperature", 0.1, 4.0, 0.3)
|
59 |
+
top_p = st.slider("Top-p", 0.1, 1.0, 0.6)
|
60 |
+
|
61 |
+
# After sidebar configuration and session_state initialization
|
62 |
+
if st.session_state.get("active_function") != "Edu-Scraper":
|
63 |
+
st.session_state.messages = []
|
64 |
+
st.session_state.active_function = "Edu-Scraper"
|
65 |
+
|
66 |
+
# Main interface
|
67 |
+
st.title("Edu Scraper with RAG")
|
68 |
+
st.caption("Powered by Hugging Face Inference API - Configure parameters in the sidebar.")
|
69 |
+
|
70 |
+
# URL input section (instead of PDF upload)
|
71 |
+
url = st.text_input("Enter URL for scraping:")
|
72 |
+
|
73 |
+
if url:
|
74 |
+
try:
|
75 |
+
with st.spinner("Loading page..."):
|
76 |
+
documents = SeleniumURLLoader(urls=[url]).load()
|
77 |
+
with st.spinner("Splitting content..."):
|
78 |
+
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200, add_start_index=True)
|
79 |
+
chunks = splitter.split_documents(documents)
|
80 |
+
# Create and store vector store
|
81 |
+
vector_store = InMemoryVectorStore.from_documents(chunks, embedding_model)
|
82 |
+
st.session_state.vector_store = vector_store
|
83 |
+
st.success("Page processed and indexed successfully!")
|
84 |
+
except Exception as e:
|
85 |
+
st.error(f"Error processing URL: {str(e)}")
|
86 |
+
|
87 |
+
def render_message(content):
|
88 |
+
# If content is enclosed by $$, render as LaTeX; else as markdown.
|
89 |
+
if content.strip().startswith("$$") and content.strip().endswith("$$"):
|
90 |
+
st.latex(content.strip()[2:-2])
|
91 |
+
else:
|
92 |
+
st.markdown(content)
|
93 |
+
|
94 |
+
# Display chat history
|
95 |
+
for message in st.session_state.messages:
|
96 |
+
with st.chat_message(message["role"]):
|
97 |
+
render_message(message["content"])
|
98 |
+
|
99 |
+
# Function to query Hugging Face API
|
100 |
+
def query(payload, api_url):
|
101 |
+
headers = {"Authorization": f"Bearer {st.secrets['HF_TOKEN']}"}
|
102 |
+
logger.info(f"Sending request to {api_url} with payload: {payload}")
|
103 |
+
response = requests.post(api_url, headers=headers, json=payload)
|
104 |
+
logger.info(f"Received response: {response.status_code}, {response.text}")
|
105 |
+
try:
|
106 |
+
return response.json()
|
107 |
+
except requests.exceptions.JSONDecodeError:
|
108 |
+
logger.error(f"Failed to decode JSON response: {response.text}")
|
109 |
+
return None
|
110 |
+
|
111 |
+
# Handle user chat input
|
112 |
+
if prompt := st.chat_input("Type your message..."):
|
113 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
114 |
+
with st.chat_message("user"):
|
115 |
+
render_message(prompt)
|
116 |
+
try:
|
117 |
+
with st.spinner("Generating response..."):
|
118 |
+
if not st.session_state.vector_store:
|
119 |
+
st.error("Please enter a URL and process the page first.")
|
120 |
+
st.stop()
|
121 |
+
vs = st.session_state.vector_store
|
122 |
+
related_docs = vs.similarity_search(prompt, k=3)
|
123 |
+
context = "\n\n".join([doc.page_content for doc in related_docs])
|
124 |
+
full_prompt = (
|
125 |
+
f"{system_message}\n\n"
|
126 |
+
f"Context: {context}\n\n"
|
127 |
+
f"{prompt}\n\n"
|
128 |
+
f"Please provide a succinct, direct, and complete answer within {max_tokens} tokens, without extra reasoning steps. Use three sentences maximum for each question and keep the answer concise."
|
129 |
+
)
|
130 |
+
payload = {
|
131 |
+
"inputs": full_prompt,
|
132 |
+
"parameters": {
|
133 |
+
"max_new_tokens": max_tokens,
|
134 |
+
"temperature": temperature,
|
135 |
+
"top_p": top_p,
|
136 |
+
"return_full_text": False
|
137 |
+
}
|
138 |
+
}
|
139 |
+
api_url = f"https://api-inference.huggingface.co/models/{selected_model}"
|
140 |
+
output = query(payload, api_url)
|
141 |
+
if output and isinstance(output, list) and len(output) > 0 and "generated_text" in output[0]:
|
142 |
+
assistant_response = output[0]["generated_text"].strip()
|
143 |
+
with st.chat_message("assistant"):
|
144 |
+
render_message(assistant_response)
|
145 |
+
st.session_state.messages.append({
|
146 |
+
"role": "assistant",
|
147 |
+
"content": assistant_response
|
148 |
+
})
|
149 |
+
else:
|
150 |
+
st.error("No response generated - please try again")
|
151 |
+
except Exception as e:
|
152 |
+
logger.error(f"Error: {str(e)}", exc_info=True)
|
153 |
+
st.error(f"An error occurred: {str(e)}")
|
Hellopage.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import os
|
3 |
+
|
4 |
+
st.set_page_config(
|
5 |
+
page_title="Welcome to Edu_AI",
|
6 |
+
page_icon="👋",
|
7 |
+
)
|
8 |
+
|
9 |
+
# Sidebar navigation
|
10 |
+
st.sidebar.title("Navigation")
|
11 |
+
# Updated navigation options to include AI Researcher
|
12 |
+
page = st.sidebar.selectbox("Go to", ["Welcome", "PDF Chatbot with RAG", "Edu Scraper with RAG", "Edu Researcher"])
|
13 |
+
|
14 |
+
if page == "Welcome":
|
15 |
+
st.write("# Welcome to Edu_AI 👋")
|
16 |
+
st.sidebar.success("Select a feature from the sidebar to get started.")
|
17 |
+
|
18 |
+
st.markdown(
|
19 |
+
"""
|
20 |
+
Edu_AI is a streamlit web application that is powered by artificial intelligence
|
21 |
+
functions for both research and educational purposes.
|
22 |
+
**👈 Select a demo from the sidebar** to see some examples
|
23 |
+
of what Edu_AI can do!
|
24 |
+
|
25 |
+
### Made by the talented batch 10 UIT Section-A students group 7
|
26 |
+
"""
|
27 |
+
)
|
28 |
+
st.image("webp.png", caption="Welcome to Edu_AI")
|
29 |
+
elif page == "PDF Chatbot with RAG":
|
30 |
+
# Run pdf_rag.py
|
31 |
+
with open(os.path.join(os.path.dirname(__file__), 'pdf_rag.py')) as f:
|
32 |
+
exec(f.read())
|
33 |
+
elif page == "Edu Scraper with RAG":
|
34 |
+
# Run Edu-Scraper.py
|
35 |
+
with open(os.path.join(os.path.dirname(__file__), 'Edu-Scraper.py')) as f:
|
36 |
+
exec(f.read())
|
37 |
+
elif page == "Edu Researcher":
|
38 |
+
# Run Edu-Researcher.py
|
39 |
+
with open(os.path.join(os.path.dirname(__file__), 'Edu-Researcher.py')) as f:
|
40 |
+
exec(f.read())
|
README.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Chat with PDF
|
2 |
+
|
3 |
+
# Pre-requisites
|
4 |
+
Install Ollama on your local machine from the [official website](https://ollama.com/). And then pull the Deepseek model:
|
5 |
+
|
6 |
+
```bash
|
7 |
+
ollama pull deepseek-r1:14b
|
8 |
+
```
|
9 |
+
|
10 |
+
Install the dependencies using pip:
|
11 |
+
|
12 |
+
```bash
|
13 |
+
pip install -r requirements.txt
|
14 |
+
```
|
15 |
+
|
16 |
+
# Run
|
17 |
+
Run the Streamlit app:
|
18 |
+
|
19 |
+
```bash
|
20 |
+
streamlit run pdf_rag.py
|
21 |
+
```
|
chromedriver.exe
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:bbef5f36638e7f728ed79dbf72043c5651759633fd5ac355fa44cd7aa853a113
|
3 |
+
size 18541056
|
pdf_rag.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import requests
|
3 |
+
import logging
|
4 |
+
import os
|
5 |
+
from langchain_community.document_loaders import PDFPlumberLoader
|
6 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
7 |
+
from langchain_community.vectorstores import InMemoryVectorStore
|
8 |
+
from langchain.embeddings import HuggingFaceEmbeddings
|
9 |
+
|
10 |
+
# Configure logging
|
11 |
+
logging.basicConfig(level=logging.INFO)
|
12 |
+
logger = logging.getLogger(__name__)
|
13 |
+
|
14 |
+
# Initialize session state for chat history and vector store
|
15 |
+
if "messages" not in st.session_state:
|
16 |
+
st.session_state.messages = []
|
17 |
+
if "vector_store" not in st.session_state:
|
18 |
+
st.session_state.vector_store = None
|
19 |
+
|
20 |
+
# Refresh messages if switching to PDF Chat (pdf_rag)
|
21 |
+
if st.session_state.get("active_function") != "pdf_rag":
|
22 |
+
st.session_state.messages = []
|
23 |
+
st.session_state.active_function = "pdf_rag"
|
24 |
+
|
25 |
+
# Set up PDF directory and embedding model
|
26 |
+
pdfs_directory = "chat-with-pdf\pdfs"
|
27 |
+
os.makedirs(pdfs_directory, exist_ok=True)
|
28 |
+
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
29 |
+
|
30 |
+
# Sidebar configuration
|
31 |
+
with st.sidebar:
|
32 |
+
st.header("Model Configuration")
|
33 |
+
st.markdown("[Get HuggingFace Token](https://huggingface.co/settings/tokens)")
|
34 |
+
|
35 |
+
# Dropdown to select model
|
36 |
+
model_options = [
|
37 |
+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
|
38 |
+
"Qwen/Qwen2.5-72B-Instruct"
|
39 |
+
]
|
40 |
+
selected_model = st.selectbox("Select Model", model_options, index=0)
|
41 |
+
|
42 |
+
system_message = st.text_area(
|
43 |
+
"System Message",
|
44 |
+
value="You are an assistant for question-answering tasks created by ruslanmv.com. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know.Use three sentences maximum for each question and keep the answer concise.",
|
45 |
+
height=100
|
46 |
+
)
|
47 |
+
max_tokens = st.slider("Max Tokens", 10, 4000, 300)
|
48 |
+
temperature = st.slider("Temperature", 0.1, 4.0, 0.3)
|
49 |
+
top_p = st.slider("Top-p", 0.1, 1.0, 0.6)
|
50 |
+
|
51 |
+
# Main interface
|
52 |
+
st.title(u"\U0001F4D1 PDF Chatbot with RAG") # fixed emoji encoding
|
53 |
+
st.caption("Powered by Hugging Face Inference API - You can configure the temperature,tokens and top-p values in the sidebar.")
|
54 |
+
|
55 |
+
# PDF upload section
|
56 |
+
uploaded_file = st.file_uploader(
|
57 |
+
"Upload a PDF for context",
|
58 |
+
type="pdf",
|
59 |
+
accept_multiple_files=False
|
60 |
+
)
|
61 |
+
|
62 |
+
if uploaded_file:
|
63 |
+
try:
|
64 |
+
# Save uploaded PDF
|
65 |
+
pdf_path = os.path.join(pdfs_directory, uploaded_file.name)
|
66 |
+
with open(pdf_path, "wb") as f:
|
67 |
+
f.write(uploaded_file.getbuffer())
|
68 |
+
|
69 |
+
# Load and process PDF
|
70 |
+
loader = PDFPlumberLoader(pdf_path)
|
71 |
+
documents = loader.load()
|
72 |
+
|
73 |
+
# Split text into chunks
|
74 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
75 |
+
chunk_size=1000,
|
76 |
+
chunk_overlap=200
|
77 |
+
)
|
78 |
+
chunks = text_splitter.split_documents(documents)
|
79 |
+
|
80 |
+
# Create and store vector store
|
81 |
+
vector_store = InMemoryVectorStore.from_documents(chunks, embedding_model)
|
82 |
+
st.session_state.vector_store = vector_store
|
83 |
+
st.success("PDF processed and indexed successfully!")
|
84 |
+
except PermissionError:
|
85 |
+
st.error(f"Permission denied: Unable to save the file to {pdf_path}. Please check the directory permissions.")
|
86 |
+
except Exception as e:
|
87 |
+
st.error(f"Error processing PDF: {str(e)}")
|
88 |
+
|
89 |
+
# Display chat history
|
90 |
+
for message in st.session_state.messages:
|
91 |
+
with st.chat_message(message["role"]):
|
92 |
+
st.markdown(message["content"])
|
93 |
+
|
94 |
+
# Function to query Hugging Face API
|
95 |
+
def query(payload, api_url):
|
96 |
+
headers = {"Authorization": f"Bearer {st.secrets['HF_TOKEN']}"}
|
97 |
+
logger.info(f"Sending request to {api_url} with payload: {payload}")
|
98 |
+
response = requests.post(api_url, headers=headers, json=payload)
|
99 |
+
logger.info(f"Received response: {response.status_code}, {response.text}")
|
100 |
+
try:
|
101 |
+
return response.json()
|
102 |
+
except requests.exceptions.JSONDecodeError:
|
103 |
+
logger.error(f"Failed to decode JSON response: {response.text}")
|
104 |
+
return None
|
105 |
+
|
106 |
+
# Handle user input
|
107 |
+
if prompt := st.chat_input("Type your message..."):
|
108 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
109 |
+
|
110 |
+
with st.chat_message("user"):
|
111 |
+
st.markdown(prompt)
|
112 |
+
|
113 |
+
try:
|
114 |
+
with st.spinner("Generating response..."):
|
115 |
+
# Check if vector store is available
|
116 |
+
if not st.session_state.vector_store:
|
117 |
+
st.error("Please upload a PDF first to provide context.")
|
118 |
+
st.stop()
|
119 |
+
|
120 |
+
# Retrieve relevant documents
|
121 |
+
vector_store = st.session_state.vector_store
|
122 |
+
related_docs = vector_store.similarity_search(prompt, k=3)
|
123 |
+
|
124 |
+
# Build context
|
125 |
+
context = "\n\n".join([doc.page_content for doc in related_docs])
|
126 |
+
|
127 |
+
# Prepare full prompt with an instruction for brevity and completeness
|
128 |
+
full_prompt = (
|
129 |
+
f"{system_message}\n\n"
|
130 |
+
f"Context: {context}\n\n"
|
131 |
+
f"{prompt}\n\n"
|
132 |
+
f"Please provide a succinct, direct, and complete answer within {max_tokens} tokens, without extra reasoning steps. Use three sentences maximum for each question and keep the answer concise."
|
133 |
+
)
|
134 |
+
|
135 |
+
# Prepare API payload
|
136 |
+
payload = {
|
137 |
+
"inputs": full_prompt,
|
138 |
+
"parameters": {
|
139 |
+
"max_new_tokens": max_tokens,
|
140 |
+
"temperature": temperature,
|
141 |
+
"top_p": top_p,
|
142 |
+
"return_full_text": False
|
143 |
+
}
|
144 |
+
}
|
145 |
+
|
146 |
+
# Query API
|
147 |
+
api_url = f"https://api-inference.huggingface.co/models/{selected_model}"
|
148 |
+
output = query(payload, api_url)
|
149 |
+
|
150 |
+
# Handle response
|
151 |
+
if output and isinstance(output, list) and len(output) > 0:
|
152 |
+
if 'generated_text' in output[0]:
|
153 |
+
assistant_response = output[0]['generated_text'].strip()
|
154 |
+
|
155 |
+
with st.chat_message("assistant"):
|
156 |
+
st.markdown(assistant_response)
|
157 |
+
|
158 |
+
st.session_state.messages.append({
|
159 |
+
"role": "assistant",
|
160 |
+
"content": assistant_response
|
161 |
+
})
|
162 |
+
else:
|
163 |
+
st.error("Unexpected response format from the model")
|
164 |
+
else:
|
165 |
+
st.error("No response generated - please try again")
|
166 |
+
|
167 |
+
except Exception as e:
|
168 |
+
logger.error(f"Error: {str(e)}", exc_info=True)
|
169 |
+
st.error(f"An error occurred: {str(e)}")
|
requirements.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
langchain_core
|
3 |
+
langchain_community
|
4 |
+
langchain_ollama
|
5 |
+
duckduckgo_search
|
6 |
+
langgraph
|
7 |
+
pdfplumber
|
8 |
+
selenium
|
9 |
+
unstructured
|
10 |
+
chromedriver
|
11 |
+
sentence-transformers
|
webp.png
ADDED
![]() |