Upload 8 files
Browse files- .env +6 -0
- Q&A_cleaned.json +158 -0
- app.py +66 -0
- auto_tester.py +79 -0
- pages/admin_dashboard.py +93 -0
- qa_loader.py +38 -0
- rag_chain.py +121 -0
- requirements.txt +12 -0
.env
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ✅ API Key'leri doğrudan yazdık (şimdilik)
|
| 2 |
+
OPENAI_API_KEY = "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"
|
| 3 |
+
TAVILY_API_KEY = "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"
|
| 4 |
+
|
| 5 |
+
ADMIN_USERNAME=superadmin
|
| 6 |
+
ADMIN_PASSWORD=admin123
|
Q&A_cleaned.json
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"QUESTION": "Where can I find the timetable?",
|
| 4 |
+
"ANSWER": "The timetable is available in the USOSweb (University Study-Oriented System).\u00a0The link to the USOSweb is available on the University\u2019s website, in the Intranet tab. Students can see their individual timetable after logging the USOS system."
|
| 5 |
+
},
|
| 6 |
+
{
|
| 7 |
+
"QUESTION": "Where can I find information about the exam session?",
|
| 8 |
+
"ANSWER": "Exam session dates are given in the organisation of the academic year. Each lecturer is obliged to inform students about the date of the exam / final test."
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"QUESTION": "How can I collect my student ID card?",
|
| 12 |
+
"ANSWER": "Student ID cards for students of the 1st semester can be collected at the reception of the University, the remaining cards should be collected at the Dean\u2019s Office."
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"QUESTION": "When will the timetable be available?",
|
| 16 |
+
"ANSWER": "According to the Regulations, the timetable is published two weeks before the beginning of courses."
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"QUESTION": "How can I find a lecturer?",
|
| 20 |
+
"ANSWER": "All information regarding courses is provided in the timetable on the USOSweb. Information on the lecturer (name and surname) is given in the description of each course and that is where you can check his/her timetable or send him/her an e-mail."
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"QUESTION": "I need help with applying for a Residence Card. Can anyone help me?",
|
| 24 |
+
"ANSWER": "Yes, you may come to room 307. They can help you with documents."
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"QUESTION": "I don\u2019t have a language certificate, can I pass an English exam at the University?",
|
| 28 |
+
"ANSWER": "Yes, you may pass an exam at the university. You may visit room 308, and register there for the exam."
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"QUESTION": "I don\u2019t have a medical certificate, can I do that at the university?",
|
| 32 |
+
"ANSWER": "Yes,\u00a0 if you do not have a medical certificate, you may get it from our University doctor (the cost is 100 PLN)."
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"QUESTION": "What is a medical certificate?",
|
| 36 |
+
"ANSWER": "It is a certificate from your doctor stating that you don\u2019t have any restrictions to conducting your studies."
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"QUESTION": "I need to submit my original documents. Where do I need to go?",
|
| 40 |
+
"ANSWER": "After arrival, you have 2 days to submit your original documents to the Recruitment office, room 28."
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"QUESTION": "Can someone help to show me around Warsaw?",
|
| 44 |
+
"ANSWER": "Yes, we have a Mentoring program, so you can apply and get assigned a Mentor, at the following e-mail address "
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"QUESTION": "I am a new student coming from abroad. I need someone to pick me up from the airport.",
|
| 48 |
+
"ANSWER": "You can apply for the Mentoring program, and you will be assigned a person who will help you get comfortable in Poland. You can apply here:\u00a0https://studentactivity.vistula.edu.pl/"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"QUESTION": "I want accommodation, where can I book it? Do you have a dormitory?",
|
| 52 |
+
"ANSWER": "In order to find out full information about accommodation, Please contact accommodation@vistula.edu.pl. Vistula offers private dormitories, as well as our own dormitory."
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"QUESTION": "I want to apply for Erasmus",
|
| 56 |
+
"ANSWER": "Please, find detailed information about Erasmus here \u2013\u00a0https://www.vistula.edu.pl/en/students/erasmus or, alternatively, you may visit room 123."
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"QUESTION": "Do I need to pay the registration fee first?",
|
| 60 |
+
"ANSWER": "No, we will send you the invoice with the tuition fee, registration fee, and student card fee."
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"QUESTION": "I applied, how can I pay?",
|
| 64 |
+
"ANSWER": "Don\u2019t worry, the university will send your invoice after reviewing your documents. The invoice can be found in the application section: Payments."
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"QUESTION": "How to reset your e-mail password?",
|
| 68 |
+
"ANSWER": "Write to ict-help@vistula.edu.pl. Unless you want to change them but know the old ones, then you can do it yourself on the Vistula mail site."
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"QUESTION": "How to log into the university system?",
|
| 72 |
+
"ANSWER": "After submitting the documents to the Recruitment Department and admission to studies, within a few days of registration, you will receive logins to individual university systems along with detailed instructions on how to use them."
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"QUESTION": "My account is inactive, what should I do?",
|
| 76 |
+
"ANSWER": "If you have already received a login and password and you cannot log in, please contact our IT department. New students who have not received an email with information on how to log in, should contact the Admissions Department. Students who continue their studies and their account is no longer active after the semester, should contact their Field Supervisor at the Dean\u2019s Office."
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"QUESTION": "How to log into Vistula systems?",
|
| 80 |
+
"ANSWER": "You can log into all systems with one account \u2013 an e-mail account in the @stu.vistula.edu.pl domain."
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"QUESTION": "I do not have or have a incorrect ID, what should I do?",
|
| 84 |
+
"ANSWER": "Please send an e-mail to the following address: ict-help@vistula.edu.pl"
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"QUESTION": "Where are my grades?",
|
| 88 |
+
"ANSWER": "In the USOSweb system."
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"QUESTION": "Is there any department to help me prepare for job interviews?",
|
| 92 |
+
"ANSWER": "Yes. We have a career office in university can help you about your job interviews. Our Career Advisor will be happy to give you tips on preparing for job interviews."
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"QUESTION": "Is there any department to help me prepare an effective CV?",
|
| 96 |
+
"ANSWER": "Of course. CV consultations are available in the Careers and Internship Department. Each student can count on the help of an experienced career counsellor."
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"QUESTION": "Can the University help me with finding a future job?",
|
| 100 |
+
"ANSWER": "Yes, we do have a Careers Department. They can help you to draw up a CV, and find an internship or job."
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"QUESTION": "Where do I have to submit documents for the internship?",
|
| 104 |
+
"ANSWER": "Documents of completed internships students of VSH submit to the Career and Internship Department (1st floor, room 116). On the other hand, VU students send scanned documents to the e-mail address of their tutor."
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"QUESTION": "Is Poland cold?",
|
| 108 |
+
"ANSWER": "Poland experiences cold winters, especially in the northern and eastern regions, with temperatures often dropping below freezing. Summers are milder and more comfortable."
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"QUESTION": "How can I buy public transportation tickets?",
|
| 112 |
+
"ANSWER": "You can purchase tickets from ticket vending machines located at some bus stops and metro stations."
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"QUESTION": "What types of public transportation are used in Warsaw?",
|
| 116 |
+
"ANSWER": "In Warsaw, public transportation includes buses, trams, the metro, and trains."
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"QUESTION": "Are there single-use public transportation tickets?",
|
| 120 |
+
"ANSWER": "There are two types of single-use public transportation tickets. One covers a 20-minute journey, and the other covers a 75-minute journey."
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"QUESTION": "How can I travel from the airport to the city center?",
|
| 124 |
+
"ANSWER": "You can travel from the airport to the city center using public transportation such as buses and trains. Additionally, taxis are available at the airport."
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"QUESTION": "How can I use a taxi?",
|
| 128 |
+
"ANSWER": "Taking a taxi in Warsaw is easy. You can use applications like Uber, Bolt, Freenow to call a taxi at your desired location and time."
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"QUESTION": "What\u2019s the best way to get to the university?",
|
| 132 |
+
"ANSWER": "The most convenient way to reach the university is by using the metro and bus."
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"QUESTION": "Is there a metro station near the university?",
|
| 136 |
+
"ANSWER": "There is a Stok\u0142osy metro station on the M1 route, just 3 minutes away from the university."
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"QUESTION": "How can I travel from the university to the city center?",
|
| 140 |
+
"ANSWER": "You can reach the city center from the university in a short time, approximately 20 minutes, by using the metro."
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"QUESTION": "Is there a discount for students in public transportation?",
|
| 144 |
+
"ANSWER": "In Poland, students with a valid student card get a 50% discount on public transport fares."
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"QUESTION": "Where can I get a SIM card?",
|
| 148 |
+
"ANSWER": "In Poland, there are several mobile operators. You can visit the stores of major brands such as Play, Orange, and T-Mobile to obtain a SIM card."
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"QUESTION": " How can I open a bank account?",
|
| 152 |
+
"ANSWER": "Each bank in Poland has its own customer policies, so the required documents to open an account may vary. Visit the bank you're interested in and submit the necessary documents to easily open an account."
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"QUESTION": "Can I have accounts in currencies other than PLN in my bank account?",
|
| 156 |
+
"ANSWER": "While it varies by bank, you can typically open accounts in currencies other than PLN, such as euros and dollars."
|
| 157 |
+
}
|
| 158 |
+
]
|
app.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import streamlit as st
|
| 4 |
+
from qa_loader import load_qa_and_create_vectorstore
|
| 5 |
+
from rag_chain import generate_response
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
# 🔹 Load environment variables
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
# 🔹 Streamlit Page Configuration
|
| 12 |
+
st.set_page_config(page_title="Vistula University AI Assistant", layout="centered")
|
| 13 |
+
|
| 14 |
+
# 🔹 Title and Description
|
| 15 |
+
st.title("📚 Vistula University AI Assistant")
|
| 16 |
+
st.write("🚀 Ask me anything about Vistula University!")
|
| 17 |
+
|
| 18 |
+
# 🔹 Retrieve Data (Cached for Performance)
|
| 19 |
+
@st.cache_resource
|
| 20 |
+
def get_retriever():
|
| 21 |
+
return load_qa_and_create_vectorstore()
|
| 22 |
+
|
| 23 |
+
retriever = get_retriever()
|
| 24 |
+
|
| 25 |
+
if isinstance(retriever, tuple):
|
| 26 |
+
retriever = retriever[0]
|
| 27 |
+
|
| 28 |
+
# 🔹 Start or Load Chat History
|
| 29 |
+
if "chat_history" not in st.session_state:
|
| 30 |
+
st.session_state.chat_history = []
|
| 31 |
+
|
| 32 |
+
# 🔹 Display Chat History
|
| 33 |
+
st.write("### 🗂️ Chat History")
|
| 34 |
+
|
| 35 |
+
for entry in st.session_state.chat_history:
|
| 36 |
+
with st.chat_message("user"):
|
| 37 |
+
st.write(entry["question"])
|
| 38 |
+
with st.chat_message("assistant"):
|
| 39 |
+
st.write(entry["answer"])
|
| 40 |
+
|
| 41 |
+
# 🔹 User Input
|
| 42 |
+
query = st.chat_input("Ask your question about Vistula University!")
|
| 43 |
+
|
| 44 |
+
# 🔹 Process When User Submits a Question
|
| 45 |
+
if query:
|
| 46 |
+
with st.spinner("🤖 Thinking..."):
|
| 47 |
+
response = generate_response(retriever, query)
|
| 48 |
+
|
| 49 |
+
# 🔹 Add to Chat History
|
| 50 |
+
st.session_state.chat_history.append({
|
| 51 |
+
"question": query,
|
| 52 |
+
"answer": response
|
| 53 |
+
})
|
| 54 |
+
|
| 55 |
+
# 🔹 Display User Question and AI Response
|
| 56 |
+
with st.chat_message("user"):
|
| 57 |
+
st.write(query)
|
| 58 |
+
with st.chat_message("assistant"):
|
| 59 |
+
placeholder = st.empty()
|
| 60 |
+
current_text = ""
|
| 61 |
+
|
| 62 |
+
# Typing Effect
|
| 63 |
+
for word in response.split():
|
| 64 |
+
current_text += word + " "
|
| 65 |
+
placeholder.write(current_text)
|
| 66 |
+
time.sleep(0.05)
|
auto_tester.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import datetime
|
| 4 |
+
from qa_loader import load_qa_and_create_vectorstore
|
| 5 |
+
from rag_chain import generate_response
|
| 6 |
+
from rapidfuzz import fuzz # Benzerlik oranı hesaplamak için
|
| 7 |
+
|
| 8 |
+
# Log klasörünü hazırla
|
| 9 |
+
os.makedirs("logs", exist_ok=True)
|
| 10 |
+
|
| 11 |
+
# Zaman damgalı log dosyası
|
| 12 |
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
| 13 |
+
log_file = f"logs/auto_test_results_{timestamp}.txt"
|
| 14 |
+
|
| 15 |
+
# Vektör veritabanını yükle
|
| 16 |
+
retriever = load_qa_and_create_vectorstore()
|
| 17 |
+
|
| 18 |
+
# Q&A dosyasını oku
|
| 19 |
+
with open("Q&A_cleaned.json", "r", encoding="utf-8") as f:
|
| 20 |
+
qa_data = json.load(f)
|
| 21 |
+
|
| 22 |
+
# Performans istatistikleri
|
| 23 |
+
total_questions = len(qa_data)
|
| 24 |
+
correct_answers = 0
|
| 25 |
+
incorrect_answers = 0
|
| 26 |
+
|
| 27 |
+
# Minimum kabul edilebilir benzerlik oranı
|
| 28 |
+
SIMILARITY_THRESHOLD = 60 # %60 eşleşme
|
| 29 |
+
|
| 30 |
+
# Log dosyasını aç ve başlık ekle
|
| 31 |
+
with open(log_file, "w", encoding="utf-8") as log:
|
| 32 |
+
log.write(f"Auto Test Run - {timestamp}\n")
|
| 33 |
+
log.write("=" * 80 + "\n")
|
| 34 |
+
|
| 35 |
+
for idx, item in enumerate(qa_data, start=1):
|
| 36 |
+
question = item['QUESTION']
|
| 37 |
+
expected_answer = item['ANSWER']
|
| 38 |
+
|
| 39 |
+
print(f"{idx}/{total_questions} Asking: {question}")
|
| 40 |
+
ai_response = generate_response(retriever, question)
|
| 41 |
+
|
| 42 |
+
# Benzerlik oranını hesapla
|
| 43 |
+
similarity_score = fuzz.ratio(expected_answer.lower(), ai_response.lower())
|
| 44 |
+
|
| 45 |
+
if similarity_score >= SIMILARITY_THRESHOLD:
|
| 46 |
+
result = f"✅ Correct (Similarity: {similarity_score:.2f}%)"
|
| 47 |
+
correct_answers += 1
|
| 48 |
+
else:
|
| 49 |
+
result = f"❌ Incorrect (Similarity: {similarity_score:.2f}%)"
|
| 50 |
+
incorrect_answers += 1
|
| 51 |
+
|
| 52 |
+
# Log'a yaz
|
| 53 |
+
log.write(f"Question {idx}/{total_questions}:\n")
|
| 54 |
+
log.write(f"Q: {question}\n")
|
| 55 |
+
log.write(f"Expected Answer: {expected_answer}\n")
|
| 56 |
+
log.write(f"AI Response: {ai_response}\n")
|
| 57 |
+
log.write(f"Similarity: {similarity_score:.2f}%\n")
|
| 58 |
+
log.write(f"Result: {result}\n")
|
| 59 |
+
log.write("-" * 80 + "\n")
|
| 60 |
+
|
| 61 |
+
print(f"🔎 {result} - Logged")
|
| 62 |
+
|
| 63 |
+
# Test sonrası performans özeti
|
| 64 |
+
accuracy = (correct_answers / total_questions) * 100
|
| 65 |
+
|
| 66 |
+
log.write("\nTEST SUMMARY\n")
|
| 67 |
+
log.write("=" * 80 + "\n")
|
| 68 |
+
log.write(f"Total Questions: {total_questions}\n")
|
| 69 |
+
log.write(f"Correct Answers: {correct_answers}\n")
|
| 70 |
+
log.write(f"Incorrect Answers: {incorrect_answers}\n")
|
| 71 |
+
log.write(f"Accuracy: {accuracy:.2f}%\n")
|
| 72 |
+
log.write("=" * 80 + "\n")
|
| 73 |
+
|
| 74 |
+
# Sonuç özeti terminale yazdır
|
| 75 |
+
print("\n🔔 TEST COMPLETED")
|
| 76 |
+
print(f"✅ Correct: {correct_answers}")
|
| 77 |
+
print(f"❌ Incorrect: {incorrect_answers}")
|
| 78 |
+
print(f"📊 Accuracy: {accuracy:.2f}%")
|
| 79 |
+
print(f"📂 Detailed log saved to: {log_file}")
|
pages/admin_dashboard.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
from qa_loader import load_qa_and_create_vectorstore
|
| 6 |
+
from langchain_chroma import Chroma
|
| 7 |
+
|
| 8 |
+
# 🔹 Page Configuration
|
| 9 |
+
st.set_page_config(page_title="Admin Dashboard", layout="wide")
|
| 10 |
+
|
| 11 |
+
# 📂 Load available log files
|
| 12 |
+
log_dir = "logs"
|
| 13 |
+
log_files = [f for f in os.listdir(log_dir) if f.startswith("auto_test_results")]
|
| 14 |
+
|
| 15 |
+
# 🔹 Read the latest log file
|
| 16 |
+
if log_files:
|
| 17 |
+
latest_log = sorted(log_files)[-1]
|
| 18 |
+
log_path = os.path.join(log_dir, latest_log)
|
| 19 |
+
with open(log_path, "r", encoding="utf-8") as f:
|
| 20 |
+
log_data = f.readlines()
|
| 21 |
+
else:
|
| 22 |
+
log_data = []
|
| 23 |
+
|
| 24 |
+
# 📊 **Log Analysis**
|
| 25 |
+
st.title("📊 Admin Dashboard")
|
| 26 |
+
st.subheader("🔍 AI Model Log Analysis")
|
| 27 |
+
|
| 28 |
+
if log_data:
|
| 29 |
+
correct_count = sum(1 for line in log_data if "✅ Correct" in line)
|
| 30 |
+
incorrect_count = sum(1 for line in log_data if "❌ Incorrect" in line)
|
| 31 |
+
total_count = correct_count + incorrect_count
|
| 32 |
+
accuracy = (correct_count / total_count) * 100 if total_count > 0 else 0
|
| 33 |
+
|
| 34 |
+
st.metric("✅ Correct Answers", correct_count)
|
| 35 |
+
st.metric("❌ Incorrect Answers", incorrect_count)
|
| 36 |
+
st.metric("🎯 Accuracy", f"{accuracy:.2f}%")
|
| 37 |
+
|
| 38 |
+
# **Most Frequently Asked Questions**
|
| 39 |
+
question_lines = [line for line in log_data if line.startswith("Q:")]
|
| 40 |
+
question_counts = pd.Series(question_lines).value_counts().head(10)
|
| 41 |
+
|
| 42 |
+
st.subheader("📌 Most Frequently Asked Questions")
|
| 43 |
+
st.write(question_counts)
|
| 44 |
+
|
| 45 |
+
else:
|
| 46 |
+
st.warning("⚠️ No log records found. Please run the automated tests.")
|
| 47 |
+
|
| 48 |
+
# 📚 **Q&A Data Update**
|
| 49 |
+
st.subheader("📌 Update Q&A Database")
|
| 50 |
+
|
| 51 |
+
# Load existing Q&A data
|
| 52 |
+
qa_file = "Q&A_cleaned.json"
|
| 53 |
+
if os.path.exists(qa_file):
|
| 54 |
+
with open(qa_file, "r", encoding="utf-8") as f:
|
| 55 |
+
qa_data = json.load(f)
|
| 56 |
+
else:
|
| 57 |
+
qa_data = []
|
| 58 |
+
|
| 59 |
+
# Add a new question
|
| 60 |
+
new_question = st.text_input("📝 New Question:")
|
| 61 |
+
new_answer = st.text_area("📌 Answer:")
|
| 62 |
+
|
| 63 |
+
if st.button("💾 Save"):
|
| 64 |
+
if new_question and new_answer:
|
| 65 |
+
qa_data.append({"QUESTION": new_question, "ANSWER": new_answer})
|
| 66 |
+
with open(qa_file, "w", encoding="utf-8") as f:
|
| 67 |
+
json.dump(qa_data, f, indent=4)
|
| 68 |
+
st.success("✅ New question added!")
|
| 69 |
+
else:
|
| 70 |
+
st.warning("⚠️ Please enter both a question and an answer.")
|
| 71 |
+
|
| 72 |
+
# **Edit Incorrect Answers**
|
| 73 |
+
st.subheader("🔄 Edit Incorrect Answers")
|
| 74 |
+
|
| 75 |
+
if qa_data:
|
| 76 |
+
question_list = [q["QUESTION"] for q in qa_data]
|
| 77 |
+
selected_question = st.selectbox("🔎 Select Question to Edit:", question_list)
|
| 78 |
+
|
| 79 |
+
selected_item = next((q for q in qa_data if q["QUESTION"] == selected_question), None)
|
| 80 |
+
updated_answer = st.text_area("✏️ Updated Answer:", selected_item["ANSWER"] if selected_item else "")
|
| 81 |
+
|
| 82 |
+
if st.button("📝 Update"):
|
| 83 |
+
if selected_item:
|
| 84 |
+
selected_item["ANSWER"] = updated_answer
|
| 85 |
+
with open(qa_file, "w", encoding="utf-8") as f:
|
| 86 |
+
json.dump(qa_data, f, indent=4)
|
| 87 |
+
st.success("✅ Answer updated!")
|
| 88 |
+
|
| 89 |
+
# **Update Vector Database**
|
| 90 |
+
st.subheader("🔄 Update Vector Database")
|
| 91 |
+
if st.button("📥 Re-train"):
|
| 92 |
+
retriever = load_qa_and_create_vectorstore()
|
| 93 |
+
st.success("✅ Vector database successfully updated!")
|
qa_loader.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from langchain.schema import Document
|
| 4 |
+
from langchain.text_splitter import CharacterTextSplitter
|
| 5 |
+
from langchain_chroma import Chroma
|
| 6 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 7 |
+
|
| 8 |
+
# 🔹 Daha güçlü bir embedding modeli kullanarak eşleşmeleri iyileştiriyoruz
|
| 9 |
+
embedding_model = HuggingFaceEmbeddings(
|
| 10 |
+
model_name="sentence-transformers/all-mpnet-base-v2",
|
| 11 |
+
encode_kwargs={"normalize_embeddings": True}
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
# 🔹 Q&A verisini yükleyip vektör veritabanı oluştur
|
| 15 |
+
def load_qa_and_create_vectorstore():
|
| 16 |
+
with open("Q&A_cleaned.json", "r", encoding="utf-8") as f:
|
| 17 |
+
qa_data = json.load(f)
|
| 18 |
+
|
| 19 |
+
documents = [
|
| 20 |
+
Document(
|
| 21 |
+
page_content=f"Question: {item['QUESTION']}\nAnswer: {item['ANSWER']}",
|
| 22 |
+
metadata={}
|
| 23 |
+
)
|
| 24 |
+
for item in qa_data
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
text_splitter = CharacterTextSplitter(chunk_size=800, chunk_overlap=200)
|
| 28 |
+
split_docs = text_splitter.split_documents(documents)
|
| 29 |
+
|
| 30 |
+
persist_directory = "./vistula_chroma"
|
| 31 |
+
|
| 32 |
+
# 🔹 Eğer eski veritabanı varsa, yeni veriyle yeniden oluştur
|
| 33 |
+
if os.path.exists(persist_directory):
|
| 34 |
+
os.system("rm -rf vistula_chroma") # Eski vektör veritabanını siliyoruz
|
| 35 |
+
|
| 36 |
+
vectordb = Chroma.from_documents(split_docs, embedding_model, persist_directory=persist_directory)
|
| 37 |
+
|
| 38 |
+
return vectordb.as_retriever()
|
rag_chain.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from langchain_openai import ChatOpenAI
|
| 3 |
+
from langchain_core.runnables import RunnableLambda
|
| 4 |
+
from langchain_core.output_parsers import StrOutputParser
|
| 5 |
+
from tavily import TavilyClient
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
import datetime
|
| 8 |
+
|
| 9 |
+
# 🔹 Load environment variables from .env file
|
| 10 |
+
load_dotenv()
|
| 11 |
+
|
| 12 |
+
# 🔹 Retrieve API keys from environment variables
|
| 13 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 14 |
+
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
|
| 15 |
+
|
| 16 |
+
if not OPENAI_API_KEY or not TAVILY_API_KEY:
|
| 17 |
+
raise ValueError("❌ API keys are missing! Please check your .env file.")
|
| 18 |
+
|
| 19 |
+
# 🔹 Initialize OpenAI and Tavily clients
|
| 20 |
+
tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
|
| 21 |
+
|
| 22 |
+
llm = ChatOpenAI(
|
| 23 |
+
model_name="llama3-8b-8192",
|
| 24 |
+
temperature=0,
|
| 25 |
+
streaming=False, # Streaming is controlled by Streamlit
|
| 26 |
+
openai_api_key=OPENAI_API_KEY,
|
| 27 |
+
openai_api_base="https://api.groq.com/openai/v1"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# 🔎 Web search function using Tavily API
|
| 31 |
+
def search_web_with_tavily(query):
|
| 32 |
+
if len(query) < 5: # Ignore very short queries
|
| 33 |
+
return ""
|
| 34 |
+
|
| 35 |
+
print(f"🔍 Sending query to Tavily: {query}")
|
| 36 |
+
search_results = tavily_client.search(query=query, max_results=3)
|
| 37 |
+
|
| 38 |
+
# Extract and format the retrieved web results
|
| 39 |
+
snippets = [f"{result['title']}: {result['content']}" for result in search_results['results'] if 'content' in result]
|
| 40 |
+
|
| 41 |
+
print("✅ Web search results retrieved!")
|
| 42 |
+
return "\n".join(snippets) if snippets else ""
|
| 43 |
+
|
| 44 |
+
# 📝 Prompt function for AI response generation
|
| 45 |
+
def prompt_fn(query: str, context: str, web_context: str = "") -> str:
|
| 46 |
+
"""
|
| 47 |
+
This is the main prompt template for the AI assistant.
|
| 48 |
+
|
| 49 |
+
The assistant must:
|
| 50 |
+
- Prioritize university knowledge first.
|
| 51 |
+
- Use web search only if internal knowledge is insufficient.
|
| 52 |
+
- If no relevant information is found, respond with:
|
| 53 |
+
"I’m sorry, but I don’t have information on this topic."
|
| 54 |
+
- Avoid unnecessary introductions, greetings, or explanations.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
# Include web search results only if available
|
| 58 |
+
search_part = f"\nAdditionally, I found the following information from the web:\n{web_context}\n" if web_context else ""
|
| 59 |
+
|
| 60 |
+
return f"""
|
| 61 |
+
Below is the available information for answering student inquiries about Vistula University.
|
| 62 |
+
|
| 63 |
+
🔹 Follow this order when answering:
|
| 64 |
+
1️⃣ **Use internal university knowledge first.**
|
| 65 |
+
2️⃣ **If internal data lacks relevant details, use web search results.**
|
| 66 |
+
3️⃣ **If no useful information is found, respond with: "I’m sorry, but I don’t have information on this topic."**
|
| 67 |
+
|
| 68 |
+
🔹 Important Rules:
|
| 69 |
+
- **Do not start with introductions.** Provide the answer directly.
|
| 70 |
+
- **If no information is available, do not add lengthy explanations.**
|
| 71 |
+
- **Never make up or guess information.**
|
| 72 |
+
|
| 73 |
+
🔹 Available Information:
|
| 74 |
+
{context}
|
| 75 |
+
{search_part}
|
| 76 |
+
|
| 77 |
+
🔹 Question:
|
| 78 |
+
{query}
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
❗ **If no relevant information is found, simply say:**
|
| 82 |
+
- "I’m sorry, but I don’t have information on this topic."
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
# 🔹 Define the AI pipeline (Prompt → LLM → Output Parsing)
|
| 86 |
+
prompt_runnable = RunnableLambda(lambda inputs: prompt_fn(inputs["query"], inputs["context"], inputs.get("web_context", "")))
|
| 87 |
+
rag_chain = prompt_runnable | llm | StrOutputParser()
|
| 88 |
+
|
| 89 |
+
# 🔥 Response generation function
|
| 90 |
+
def generate_response(retriever, query):
|
| 91 |
+
# Handle short greetings separately
|
| 92 |
+
if len(query.split()) <= 2 or query.lower() in ["hi", "hello", "help", "hey", "merhaba"]:
|
| 93 |
+
return "👋 Hi there! How can I assist you today? Please ask me a specific question about Vistula University."
|
| 94 |
+
|
| 95 |
+
# Retrieve relevant documents from the knowledge base
|
| 96 |
+
relevant_docs = retriever.invoke(query)
|
| 97 |
+
context = "\n".join([doc.page_content for doc in relevant_docs])
|
| 98 |
+
|
| 99 |
+
# If no useful data is found, return a short response
|
| 100 |
+
if not relevant_docs or len(context.strip()) < 20:
|
| 101 |
+
return "I’m sorry, but I don’t have information on this topic."
|
| 102 |
+
|
| 103 |
+
# Generate response using AI
|
| 104 |
+
inputs = {"query": query, "context": context}
|
| 105 |
+
response = rag_chain.invoke(inputs).strip()
|
| 106 |
+
|
| 107 |
+
return response if response else "I’m sorry, but I don’t have information on this topic."
|
| 108 |
+
|
| 109 |
+
# 🔹 Logging function for tracking interactions
|
| 110 |
+
def log_interaction(question, answer, source):
|
| 111 |
+
log_folder = "logs"
|
| 112 |
+
os.makedirs(log_folder, exist_ok=True) # Ensure logs directory exists
|
| 113 |
+
|
| 114 |
+
log_file = os.path.join(log_folder, "chat_log.txt")
|
| 115 |
+
|
| 116 |
+
with open(log_file, "a", encoding="utf-8") as f:
|
| 117 |
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Add timestamp
|
| 118 |
+
f.write(f"{timestamp} | Question: {question}\n") # Log user question
|
| 119 |
+
f.write(f"{timestamp} | Answer: {answer}\n") # Log AI response
|
| 120 |
+
f.write(f"{timestamp} | Source: {source}\n") # Indicate data source (VectorStore/Web)
|
| 121 |
+
f.write("-" * 80 + "\n") # Separator for readability
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
langchain
|
| 2 |
+
langchain-community
|
| 3 |
+
langchain-openai
|
| 4 |
+
langchain-chroma
|
| 5 |
+
chromadb
|
| 6 |
+
huggingface-hub
|
| 7 |
+
langchain-huggingface
|
| 8 |
+
tavily-python
|
| 9 |
+
sentence-transformers
|
| 10 |
+
torch
|
| 11 |
+
streamlit
|
| 12 |
+
python-dotenv
|