StrojarAI / app.py
Amdreas's picture
Create app.py
6646147 verified
Raw History Blame Contribute Delete
5.67 kB
python
import os, io, re
import streamlit as st
from pdfminer.high_level import extract_text
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient
from qdrant_client.http import models as qmodels
import requests
st.set_page_config(page_title="EDM & Fanuc – Hybrid RAG", layout="wide")
st.title("🔧 EDM & Fanuc – Hybrid RAG (PDF + Web)")
# Sidebar configs
with st.sidebar:
st.header("🔐 Secrets")
QDRANT_URL = st.text_input("QDRANT_URL", value=os.getenv("QDRANT_URL", ""))
QDRANT_API_KEY = st.text_input("QDRANT_API_KEY", value=os.getenv("QDRANT_API_KEY", ""), type="password")
TAVILY_API_KEY = st.text_input("TAVILY_API_KEY (optional)", value=os.getenv("TAVILY_API_KEY", ""), type="password")
st.header("⚙️ Settings")
collection = st.text_input("Qdrant collection", value="docs")
topic = st.selectbox("Topic tag", ["EDM","FANUC","general"], index=0)
chunk_chars = st.slider("Chunk size (chars)", 800, 2500, 1500, 100)
overlap = st.slider("Overlap (chars)", 0, 400, 200, 20)
topk = st.slider("Top-K passages", 1, 8, 5, 1)
# Cache models
@st.cache_resource(show_spinner=False)
def get_embedder():
return SentenceTransformer("intfloat/multilingual-e5-small")
@st.cache_resource(show_spinner=False)
def get_qdrant(url, key):
return QdrantClient(url=url, api_key=key) if url else None
# Helpers
def chunk_text(text: str, size: int, ov: int):
text = re.sub(r"\s+", " ", text)
chunks, i = [], 0
step = max(1, size - ov)
while i < len(text):
chunk = text[i:i+size]
if chunk.strip():
chunks.append(chunk)
i += step
return chunks
def ensure_collection(client: QdrantClient, coll: str, dim: int = 384):
try:
client.get_collection(coll)
except Exception:
client.recreate_collection(
collection_name=coll,
vectors_config=qmodels.VectorParams(size=dim, distance=qmodels.Distance.COSINE)
)
def embed_passages(texts):
emb = get_embedder()
return emb.encode([f"passage: {t}" for t in texts], normalize_embeddings=True).tolist()
def embed_query(text: str):
emb = get_embedder()
return emb.encode([f"query: {text}"], normalize_embeddings=True)[0].tolist()
def upsert_chunks(client, coll, chunks, topic):
vectors = embed_passages(chunks)
points = []
for c, v in zip(chunks, vectors):
points.append(qmodels.PointStruct(vector=v, payload={"text": c, "topic": topic}))
client.upsert(collection_name=coll, points=points)
return len(points)
def search_local(client, coll, query, topk, filt):
qvec = embed_query(query)
qfilter = None
if filt:
must = [qmodels.FieldCondition(key=k, match=qmodels.MatchValue(value=v)) for k,v in filt.items()]
qfilter = qmodels.Filter(must=must)
res = client.search(collection_name=coll, query_vector=qvec, limit=topk, query_filter=qfilter)
hits = [{"score": r.score, "text": r.payload.get("text",""), "payload": r.payload} for r in res]
return hits
def web_search(query: str, n: int = 3):
if not TAVILY_API_KEY:
return []
r = requests.post("https://api.tavily.com/search", json={
"api_key": TAVILY_API_KEY,
"query": query,
"search_depth": "basic",
"max_results": n
}, timeout=30)
data = r.json()
out = []
for item in data.get("results", [])[:n]:
text = (item.get("title","") + "\n" + item.get("content",""))[:1500]
out.append({"url": item.get("url"), "text": text})
return out
# Tabs
ingest_tab, chat_tab = st.tabs(["📥 Ingest (PDF)", "💬 Chat"])
with ingest_tab:
st.subheader("Nahraj PDF (EDM / Fanuc)")
uploaded = st.file_uploader("Vyber PDF súbory", type=["pdf"], accept_multiple_files=True)
if st.button("Indexovať do Qdrant"):
if not (QDRANT_URL and QDRANT_API_KEY and uploaded):
st.error("Vyplň Qdrant URL/API key a nahraj aspoň 1 PDF.")
else:
client = get_qdrant(QDRANT_URL, QDRANT_API_KEY)
ensure_collection(client, collection)
total_chunks = 0
with st.spinner("Spracúvam PDF…"):
for f in uploaded:
text = extract_text(io.BytesIO(f.getvalue()))
chunks = chunk_text(text, chunk_chars, overlap)
total_chunks += upsert_chunks(client, collection, chunks, topic)
st.success(f"Hotovo. Uložených chunkov: {total_chunks}")
with chat_tab:
st.subheader("Otázka")
q = st.text_area("Zadaj otázku (EDM/Fanuc)", height=100)
do_web = st.checkbox("Povoliť web doplnenie (Tavily)", value=True)
if st.button("Spýtať sa"):
if not (QDRANT_URL and QDRANT_API_KEY and q):
st.error("Vyplň Qdrant URL/API key a otázku.")
else:
client = get_qdrant(QDRANT_URL, QDRANT_API_KEY)
filt = {"topic": topic}
hits = search_local(client, collection, q, topk, filt)
have_good_local = any(h.get("score",0) >= 0.35 for h in hits)
web_snips = web_search(q, n=3) if (do_web and not have_good_local) else []
st.markdown("### Výsledky z PDF")
for i,h in enumerate(hits):
st.write(f"[{i+1}] (score {h['score']:.2f}) {h['text'][:500]}…")
if web_snips:
st.markdown("### Výsledky z webu")
for j,w in enumerate(web_snips):
st.write(f"[W{j+1}] {w['url']} — {w['text'][:300]}…")
if not hits and not web_snips:
st.warning("Nenašli sa žiadne relevantné zdroje.")