RAG_Streamlit / app.py
Angelguava's picture
Update app.py
0c92228 verified
Raw
History Blame Contribute Delete
16.7 kB
"""
Streamlit + Groq API - 8็จฎ RAG ็ญ–็•ฅ PDF ๅ•็ญ”็ณป็ตฑ
ๅฎ‰่ฃไพ่ณด: pip install streamlit groq pypdf sentence-transformers numpy faiss-cpu scikit-learn
ๅŸท่กŒๆ–นๅผ: streamlit run rag_streamlit.py
"""
import streamlit as st
from groq import Groq
import numpy as np
from sentence_transformers import SentenceTransformer
import faiss
from pypdf import PdfReader
import re
from sklearn.feature_extraction.text import TfidfVectorizer
import io
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# RAG ๆ ธๅฟƒ้กžๅˆฅ
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class MultiStrategyRAG:
def __init__(self, api_key: str):
self.client = Groq(api_key=api_key)
self.embedding_model = SentenceTransformer(
"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)
self.chunks: list[str] = []
self.embeddings = None
self.index = None
self.tfidf_vectorizer = None
self.tfidf_matrix = None
# โ”€โ”€ ๆ–‡ไปถ่ผ‰ๅ…ฅ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def load_pdf(self, pdf_bytes: bytes) -> str:
try:
reader = PdfReader(io.BytesIO(pdf_bytes))
full_text = "".join(
(page.extract_text() or "") + "\n" for page in reader.pages
)
self.chunks = self._split_text(full_text, chunk_size=800, overlap=150)
self.embeddings = self.embedding_model.encode(
self.chunks, convert_to_numpy=True
)
dim = self.embeddings.shape[1]
self.index = faiss.IndexFlatL2(dim)
self.index.add(self.embeddings.astype("float32"))
self.tfidf_vectorizer = TfidfVectorizer(max_features=1000)
self.tfidf_matrix = self.tfidf_vectorizer.fit_transform(self.chunks)
return f"โœ… ๆˆๅŠŸ่ผ‰ๅ…ฅ PDF๏ผๅ…ฑ {len(reader.pages)} ้ ๏ผŒๅˆ†ๅ‰ฒ็‚บ {len(self.chunks)} ๅ€‹็‰‡ๆฎต"
except Exception as e:
return f"โŒ ่ผ‰ๅ…ฅๅคฑๆ•—๏ผš{e}"
def _split_text(self, text: str, chunk_size: int, overlap: int) -> list[str]:
chunks, start = [], 0
while start < len(text):
chunk = re.sub(r"\s+", " ", text[start : start + chunk_size]).strip()
if chunk:
chunks.append(chunk)
start += chunk_size - overlap
return chunks
# โ”€โ”€ 8 ็จฎ RAG ็ญ–็•ฅ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def strategy_1_basic_similarity(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ1๏ผšๅŸบ็คŽ่ชžๆ„็›ธไผผๅบฆๆœๅฐ‹"""
vec = self.embedding_model.encode([query]).astype("float32")
_, indices = self.index.search(vec, top_k)
return [self.chunks[i] for i in indices[0]]
def strategy_2_tfidf(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ2๏ผšTF-IDF ้—œ้ต่ฉžๆœๅฐ‹"""
qvec = self.tfidf_vectorizer.transform([query])
scores = (self.tfidf_matrix * qvec.T).toarray().flatten()
top_idx = scores.argsort()[-top_k:][::-1]
return [self.chunks[i] for i in top_idx]
def strategy_3_hybrid(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ3๏ผšๆททๅˆๆœๅฐ‹๏ผˆ่ชžๆ„ + TF-IDF๏ผ‰"""
vec = self.embedding_model.encode([query]).astype("float32")
_, sem_idx = self.index.search(vec, top_k * 2)
qvec = self.tfidf_vectorizer.transform([query])
tfidf_scores = (self.tfidf_matrix * qvec.T).toarray().flatten()
tfidf_idx = tfidf_scores.argsort()[-top_k * 2 :][::-1]
combined = list(dict.fromkeys(sem_idx[0].tolist() + tfidf_idx.tolist()))
return [self.chunks[i] for i in combined[:top_k]]
def strategy_4_reranking(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ4๏ผš้‡ๆ–ฐๆŽ’ๅบ๏ผˆLLM ่ฉ•ๅˆ†้‡ๆŽ’๏ผ‰"""
candidates = self.strategy_1_basic_similarity(query, top_k=top_k * 2)
reranked = []
for chunk in candidates:
prompt = (
f"ๅ•้กŒ๏ผš{query}\n\nๆ–‡ๆœฌ๏ผš{chunk[:200]}...\n\n"
"้€™ๆฎตๆ–‡ๆœฌ่ˆ‡ๅ•้กŒ็š„็›ธ้—œๅบฆ(0-10)๏ผš"
)
try:
resp = self.client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[{"role": "user", "content": prompt}],
max_tokens=10,
temperature=0,
)
raw = resp.choices[0].message.content.strip()
nums = re.findall(r"\d+", raw)
score = float(nums[0]) if nums else 0
except Exception:
score = 0
reranked.append((chunk, score))
reranked.sort(key=lambda x: x[1], reverse=True)
return [c for c, _ in reranked[:top_k]]
def strategy_5_multi_query(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ5๏ผšๅคšๆŸฅ่ฉขๆ“ดๅฑ•"""
prompt = f"ๅฐ‡ไปฅไธ‹ๅ•้กŒๆ”นๅฏซๆˆ3ๅ€‹็›ธ้—œไฝ†ไธๅŒ่ง’ๅบฆ็š„ๅ•้กŒ๏ผŒ็”จๆ›่กŒๅˆ†้š”๏ผš\n{query}"
try:
resp = self.client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[{"role": "user", "content": prompt}],
max_tokens=200,
temperature=0.7,
)
queries = [query] + resp.choices[0].message.content.strip().split("\n")[:3]
except Exception:
queries = [query]
all_chunks: list[str] = []
for q in queries:
all_chunks.extend(self.strategy_1_basic_similarity(q, top_k=2))
return list(dict.fromkeys(all_chunks))[:top_k]
def strategy_6_contextual_compression(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ6๏ผšไธŠไธ‹ๆ–‡ๅฃ“็ธฎ"""
chunks = self.strategy_1_basic_similarity(query, top_k=top_k)
compressed = []
for chunk in chunks:
prompt = (
f"ๅพžไปฅไธ‹ๆ–‡ๆœฌไธญๆๅ–่ˆ‡ๅ•้กŒใ€Œ{query}ใ€ๆœ€็›ธ้—œ็š„1-2ๅฅ่ฉฑ๏ผš\n\n{chunk}"
)
try:
resp = self.client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[{"role": "user", "content": prompt}],
max_tokens=150,
temperature=0,
)
compressed.append(resp.choices[0].message.content.strip())
except Exception:
compressed.append(chunk[:300])
return compressed
def strategy_7_parent_child(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ7๏ผš็ˆถๅญๆ–‡ๆช”๏ผˆๅฐ็‰‡ๆฎตๅฐๆ‡‰ๅคงไธŠไธ‹ๆ–‡๏ผ‰"""
small_chunks = self._split_text(" ".join(self.chunks), chunk_size=300, overlap=50)
small_emb = self.embedding_model.encode(small_chunks, convert_to_numpy=True)
small_index = faiss.IndexFlatL2(small_emb.shape[1])
small_index.add(small_emb.astype("float32"))
vec = self.embedding_model.encode([query]).astype("float32")
_, indices = small_index.search(vec, top_k)
results = []
for idx in indices[0]:
for big in self.chunks:
if small_chunks[idx] in big:
results.append(big)
break
return list(dict.fromkeys(results))[:top_k]
def strategy_8_hypothetical_answer(self, query: str, top_k: int = 3) -> list[str]:
"""็ญ–็•ฅ8๏ผšๅ‡่จญๆ€ง็ญ”ๆกˆ๏ผˆHyDE๏ผ‰"""
prompt = f"่ซ‹ๅฐไปฅไธ‹ๅ•้กŒ็ตฆๅ‡บไธ€ๅ€‹ๅ‡่จญๆ€ง็š„็ญ”ๆกˆ๏ผˆๅณไฝฟไธ็ขบๅฎš๏ผ‰๏ผš\n{query}"
try:
resp = self.client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[{"role": "user", "content": prompt}],
max_tokens=200,
temperature=0.7,
)
hypo = resp.choices[0].message.content
except Exception:
hypo = query
vec = self.embedding_model.encode([hypo]).astype("float32")
_, indices = self.index.search(vec, top_k)
return [self.chunks[i] for i in indices[0]]
# โ”€โ”€ ็ญ”ๆกˆ็”Ÿๆˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def generate_answer(
self, query: str, strategy: str, top_k: int = 3
) -> tuple[str, str]:
if not self.chunks:
return "โŒ ่ซ‹ๅ…ˆไธŠๅ‚ณ PDF ๆช”ๆกˆ๏ผ", ""
strategy_map = {
"1. ๅŸบ็คŽ่ชžๆ„ๆœๅฐ‹": self.strategy_1_basic_similarity,
"2. TF-IDF ้—œ้ต่ฉž": self.strategy_2_tfidf,
"3. ๆททๅˆๆœๅฐ‹": self.strategy_3_hybrid,
"4. ้‡ๆ–ฐๆŽ’ๅบ": self.strategy_4_reranking,
"5. ๅคšๆŸฅ่ฉขๆ“ดๅฑ•": self.strategy_5_multi_query,
"6. ไธŠไธ‹ๆ–‡ๅฃ“็ธฎ": self.strategy_6_contextual_compression,
"7. ็ˆถๅญๆ–‡ๆช”": self.strategy_7_parent_child,
"8. ๅ‡่จญๆ€ง็ญ”ๆกˆ (HyDE)": self.strategy_8_hypothetical_answer,
}
retrieval_fn = strategy_map.get(strategy, self.strategy_1_basic_similarity)
relevant_chunks = retrieval_fn(query, top_k)
context = "\n\n---\n\n".join(relevant_chunks)
prompt = (
"่ซ‹ๆ นๆ“šไปฅไธ‹ไธŠไธ‹ๆ–‡ๅ›ž็ญ”ๅ•้กŒใ€‚ๅฆ‚ๆžœไธŠไธ‹ๆ–‡ไธญๆฒ’ๆœ‰็›ธ้—œ่ณ‡่จŠ๏ผŒ่ซ‹่ชชๆ˜Ž็„กๆณ•ๅ›ž็ญ”ใ€‚\n\n"
f"ไธŠไธ‹ๆ–‡๏ผš\n{context}\n\nๅ•้กŒ๏ผš{query}\n\n่ซ‹็”จ็น้ซ”ไธญๆ–‡่ฉณ็ดฐๅ›ž็ญ”๏ผš"
)
try:
resp = self.client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[
{"role": "system", "content": "ไฝ ๆ˜ฏๅฐˆๆฅญ็š„ๆ–‡ไปถๅˆ†ๆžๅŠฉๆ‰‹ใ€‚"},
{"role": "user", "content": prompt},
],
max_tokens=1024,
temperature=0.3,
)
answer = resp.choices[0].message.content
source_info = (
f"๐Ÿ“š ไฝฟ็”จ็ญ–็•ฅ๏ผš{strategy}\n"
f"๐Ÿ“„ ๆชข็ดข็‰‡ๆฎตๆ•ธ๏ผš{len(relevant_chunks)}\n\n"
+ "=" * 50 + "\n็›ธ้—œๆ–‡ๆœฌ็‰‡ๆฎต๏ผš\n" + "=" * 50
+ f"\n\n{context}"
)
return answer, source_info
except Exception as e:
return f"โŒ ็”Ÿๆˆ็ญ”ๆกˆๅคฑๆ•—๏ผš{e}", ""
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Streamlit ้ ้ข
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
STRATEGY_DESCRIPTIONS = {
"1. ๅŸบ็คŽ่ชžๆ„ๆœๅฐ‹": "ไฝฟ็”จๅ‘้‡้ค˜ๅผฆ็›ธไผผๅบฆ๏ผŒๅฐ‡ๅ•้กŒ่ˆ‡ๆ–‡ไปถ็‰‡ๆฎตๅฐๆฏ”๏ผŒๆ‰พๅ‡บ่ชžๆ„ๆœ€ๆŽฅ่ฟ‘็š„ๆฎต่ฝใ€‚",
"2. TF-IDF ้—œ้ต่ฉž": "ๅŸบๆ–ผ่ฉž้ ป-้€†ๆ–‡ๆช”้ ป็އ๏ผˆTF-IDF๏ผ‰็ตฑ่จˆ๏ผŒ้ฉๅˆ็ฒพ็ขบ้—œ้ตๅญ—ๅŒน้…ๅ ดๆ™ฏใ€‚",
"3. ๆททๅˆๆœๅฐ‹": "ๅŒๆ™‚ๅŸท่กŒ่ชžๆ„ๆœๅฐ‹่ˆ‡ TF-IDF๏ผŒ่žๅˆๅ…ฉ่€…็ตๆžœ๏ผŒๅ…ผ้กง่ชžๆ„่ˆ‡้—œ้ตๅญ—ใ€‚",
"4. ้‡ๆ–ฐๆŽ’ๅบ": "ๅ…ˆ็”จ่ชžๆ„ๆœๅฐ‹ๅฌๅ›žๅ€™้ธ็‰‡ๆฎต๏ผŒๅ†่ฎ“ LLM ็‚บๆฏๆฎตๆ‰“ๅˆ†้‡ๆ–ฐๆŽ’ๅบใ€‚",
"5. ๅคšๆŸฅ่ฉขๆ“ดๅฑ•": "่ฎ“ LLM ๅฐ‡ๅ•้กŒๆ”นๅฏซ็‚บๅคšๅ€‹่ง’ๅบฆ็š„ๅ•้กŒ๏ผŒๅ†ๅˆ†ๅˆฅๆœๅฐ‹ๅˆไฝต็ตๆžœใ€‚",
"6. ไธŠไธ‹ๆ–‡ๅฃ“็ธฎ": "ๅ…ˆ่ชžๆ„ๆœๅฐ‹๏ผŒๅ†่ซ‹ LLM ๅพžๆฏๆฎตไธญ่ƒๅ–่ˆ‡ๅ•้กŒๆœ€็›ธ้—œ็š„ 1-2 ๅฅใ€‚",
"7. ็ˆถๅญๆ–‡ๆช”": "ไปฅๆ›ดๅฐ็š„ๅญ็‰‡ๆฎตๆœๅฐ‹๏ผŒไฝ†ๅ›žๅ‚ณๅŒ…ๅซ่ฉฒๅญ็‰‡ๆฎต็š„ๅŽŸๅง‹ๅคงๆฎตๆ–‡ๆœฌใ€‚",
"8. ๅ‡่จญๆ€ง็ญ”ๆกˆ (HyDE)": "ๅ…ˆ่ฎ“ LLM ็”Ÿๆˆไธ€ๅ€‹ๅ‡่จญ็ญ”ๆกˆ๏ผŒ็”จๆญคๅ‡่จญ็ญ”ๆกˆ็š„ๅ‘้‡ๆœๅฐ‹ๆ–‡ไปถใ€‚",
}
EXAMPLE_QUESTIONS = [
"้€™ไปฝๆ–‡ไปถ็š„ไธป่ฆๅ…งๅฎนๆ˜ฏไป€้บผ๏ผŸ",
"ๆ–‡ไปถไธญๆๅˆฐๅ“ชไบ›้‡่ฆๆฆ‚ๅฟต๏ผŸ",
"ๆœ‰ๅ“ชไบ›้—œ้ตๆ•ธๆ“šๆˆ–็ตฑ่จˆ่ณ‡ๆ–™๏ผŸ",
"ๆ–‡ไปถ็š„็ต่ซ–ๆ˜ฏไป€้บผ๏ผŸ",
]
def get_rag(api_key: str) -> MultiStrategyRAG:
"""ๅœจ session_state ไธญๅฟซๅ– RAG ๅฏฆไพ‹๏ผˆ้ฟๅ…้‡่ค‡่ผ‰ๅ…ฅๆจกๅž‹๏ผ‰"""
if "rag" not in st.session_state:
st.session_state.rag = MultiStrategyRAG(api_key=api_key)
return st.session_state.rag
def main():
st.set_page_config(
page_title="ๅคš็ญ–็•ฅ RAG PDF ๅ•็ญ”็ณป็ตฑ",
page_icon="๐Ÿค–",
layout="wide",
)
# โ”€โ”€ ๆจ™้กŒ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
st.title("๐Ÿค– ๅคš็ญ–็•ฅ RAG PDF ๅ•็ญ”็ณป็ตฑ")
st.markdown(
"ๆŽก็”จ **8 ็จฎไธๅŒ็š„ RAG ็ญ–็•ฅ**๏ผŒ็‚บๆ‚จ็š„ PDF ๆ–‡ไปถๆไพ›ๆ™บ่ƒฝๅ•็ญ”ๆœๅ‹™๏ผ"
)
st.divider()
# โ”€โ”€ API ้‡‘้‘ฐ๏ผˆๅด้‚Šๆฌ„๏ผ‰ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
with st.sidebar:
st.header("โš™๏ธ ่จญๅฎš")
api_key = st.text_input(
"Groq API Key",
value="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",
type="password",
help="่ซ‹่ผธๅ…ฅๆ‚จ็š„ Groq API ้‡‘้‘ฐ",
)
st.divider()
st.subheader("๐Ÿ“– ็ญ–็•ฅ่ชชๆ˜Ž")
for name, desc in STRATEGY_DESCRIPTIONS.items():
with st.expander(name):
st.write(desc)
# โ”€โ”€ ๅˆๅง‹ๅŒ– RAG โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if not api_key:
st.warning("โš ๏ธ ่ซ‹ๅœจๅด้‚Šๆฌ„่ผธๅ…ฅ Groq API Key ๅพŒ็นผ็บŒใ€‚")
st.stop()
rag = get_rag(api_key)
# โ”€โ”€ ๆญฅ้ฉŸ 1๏ผšไธŠๅ‚ณ PDF โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
st.subheader("๐Ÿ“ค ๆญฅ้ฉŸ 1๏ผšไธŠๅ‚ณ PDF")
uploaded_file = st.file_uploader("้ธๆ“‡ PDF ๆช”ๆกˆ", type=["pdf"])
if uploaded_file is not None:
# ๅชๅœจๆช”ๅๆ”น่ฎŠๆ™‚้‡ๆ–ฐ่ผ‰ๅ…ฅ
if st.session_state.get("loaded_filename") != uploaded_file.name:
with st.spinner("๐Ÿ”„ ๆญฃๅœจ่ผ‰ๅ…ฅไธฆๅปบ็ซ‹็ดขๅผ•๏ผŒ่ซ‹็จๅ€™โ€ฆ"):
status = rag.load_pdf(uploaded_file.read())
st.session_state["loaded_filename"] = uploaded_file.name
st.session_state["load_status"] = status
status_msg = st.session_state.get("load_status", "")
if "โœ…" in status_msg:
st.success(status_msg)
else:
st.error(status_msg)
st.divider()
# โ”€โ”€ ๆญฅ้ฉŸ 2 & 3๏ผš็ญ–็•ฅ้ธๆ“‡ + ๆๅ• โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
col_left, col_right = st.columns([1, 2])
with col_left:
st.subheader("โš™๏ธ ๆญฅ้ฉŸ 2๏ผšRAG ็ญ–็•ฅ")
strategy = st.selectbox(
"้ธๆ“‡็ญ–็•ฅ",
list(STRATEGY_DESCRIPTIONS.keys()),
index=0,
label_visibility="collapsed",
)
st.caption(STRATEGY_DESCRIPTIONS[strategy])
top_k = st.slider(
"ๆชข็ดข็‰‡ๆฎตๆ•ธ้‡๏ผˆTop-K๏ผ‰",
min_value=1,
max_value=10,
value=3,
step=1,
)
with col_right:
st.subheader("๐Ÿ’ฌ ๆญฅ้ฉŸ 3๏ผšๆๅ•")
# ็ฏ„ไพ‹ๅ•้กŒๅฟซ้€Ÿๅกซๅ…ฅ
st.caption("๐Ÿ’ก ๅฟซ้€Ÿๅกซๅ…ฅ็ฏ„ไพ‹ๅ•้กŒ๏ผš")
example_cols = st.columns(len(EXAMPLE_QUESTIONS))
for col, q in zip(example_cols, EXAMPLE_QUESTIONS):
if col.button(q[:10] + "โ€ฆ", key=f"ex_{q}", use_container_width=True, help=q):
st.session_state["question_input"] = q
question = st.text_area(
"่ผธๅ…ฅๆ‚จ็š„ๅ•้กŒ",
value=st.session_state.get("question_input", ""),
placeholder="ไพ‹ๅฆ‚๏ผš้€™ไปฝๆ–‡ไปถ็š„ไธป่ฆๅ…งๅฎนๆ˜ฏไป€้บผ๏ผŸ",
height=100,
key="question_input",
)
ask_clicked = st.button(
"๐Ÿ” ๆๅ•",
type="primary",
use_container_width=True,
disabled=(not rag.chunks),
)
if not rag.chunks:
st.info("โ„น๏ธ ่ซ‹ๅ…ˆไธŠๅ‚ณ PDF ๆ–‡ไปถ๏ผŒๆ‰่ƒฝ้–‹ๅง‹ๆๅ•ใ€‚")
# โ”€โ”€ ็ญ”ๆกˆ่ผธๅ‡บ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if ask_clicked:
if not question.strip():
st.warning("โš ๏ธ ่ซ‹่ผธๅ…ฅๅ•้กŒๅพŒๅ†้€ๅ‡บใ€‚")
else:
with st.spinner("๐Ÿง  ๆญฃๅœจๆ€่€ƒไธญ๏ผŒ่ซ‹็จๅ€™โ€ฆ"):
answer, source_info = rag.generate_answer(question, strategy, top_k)
st.divider()
st.subheader("๐Ÿ’ก AI ๅ›ž็ญ”")
if answer.startswith("โŒ"):
st.error(answer)
else:
st.markdown(answer)
if source_info:
with st.expander("๐Ÿ“š ๆŸฅ็œ‹ๆชข็ดขๅˆฐ็š„ๆ–‡ๆœฌ็‰‡ๆฎต"):
st.text(source_info)
if __name__ == "__main__":
main()