Amdreas commited on
Commit
43692c9
·
verified ·
1 Parent(s): f1640bf

Update requirements.txt

Browse files
Files changed (1) hide show
  1. requirements.txt +185 -1
requirements.txt CHANGED
@@ -1 +1,185 @@
1
- huggingface_hub==0.25.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # requirements.txt
2
+ gradio
3
+ qdrant-client
4
+ sentence-transformers
5
+ pdfminer.six
6
+ tavily-python
7
+ requests
8
+ numpy
9
+
10
+ # app.py
11
+ import os, io, re, json
12
+ import gradio as gr
13
+ from pdfminer.high_level import extract_text
14
+ from sentence_transformers import SentenceTransformer
15
+ from qdrant_client import QdrantClient
16
+ from qdrant_client.http import models as qmodels
17
+ import requests
18
+
19
+ # ------------ Global (lazy) ------------
20
+ EMBED_MODEL_ID = "intfloat/multilingual-e5-small" # 384-dim, multilingual
21
+ _embedder = None
22
+ _client = None
23
+ _collection = None
24
+
25
+ # ------------ Helpers ------------
26
+ def get_embedder():
27
+ global _embedder
28
+ if _embedder is None:
29
+ _embedder = SentenceTransformer(EMBED_MODEL_ID)
30
+ return _embedder
31
+
32
+ def connect_qdrant(url: str, api_key: str, collection: str):
33
+ global _client, _collection
34
+ _client = QdrantClient(url=url, api_key=api_key) if url else None
35
+ _collection = collection
36
+ if _client:
37
+ try:
38
+ _client.get_collection(collection)
39
+ except Exception:
40
+ _client.recreate_collection(
41
+ collection_name=collection,
42
+ vectors_config=qmodels.VectorParams(size=384, distance=qmodels.Distance.COSINE)
43
+ )
44
+ return True
45
+
46
+ def chunk_text(text: str, size: int = 1500, overlap: int = 200):
47
+ text = re.sub(r"\s+", " ", text)
48
+ chunks, i = [], 0
49
+ step = max(1, size - overlap)
50
+ while i < len(text):
51
+ chunk = text[i:i+size]
52
+ if chunk.strip():
53
+ chunks.append(chunk)
54
+ i += step
55
+ return chunks
56
+
57
+ def embed_passages(texts):
58
+ emb = get_embedder()
59
+ return emb.encode([f"passage: {t}" for t in texts], normalize_embeddings=True).tolist()
60
+
61
+ def embed_query(text: str):
62
+ emb = get_embedder()
63
+ return emb.encode([f"query: {text}"], normalize_embeddings=True)[0].tolist()
64
+
65
+ # ------------ Ingest ------------
66
+ def ingest_pdfs(files, topic: str, chunk_chars: int, overlap: int):
67
+ if not _client:
68
+ return "❌ Najprv nastav QDRANT_URL a QDRANT_API_KEY vľavo.", None
69
+ total_chunks = 0
70
+ for f in files or []:
71
+ data = f.read()
72
+ text = extract_text(io.BytesIO(data))
73
+ chunks = chunk_text(text, chunk_chars, overlap)
74
+ vecs = embed_passages(chunks)
75
+ points = []
76
+ for c, v in zip(chunks, vecs):
77
+ points.append(qmodels.PointStruct(vector=v, payload={"text": c, "topic": topic}))
78
+ _client.upsert(collection_name=_collection, points=points)
79
+ total_chunks += len(chunks)
80
+ return f"✅ Uložené chunky: {total_chunks}", None
81
+
82
+ # ------------ Search & (optional) web ------------
83
+ def search_local(query: str, topk: int, topic: str):
84
+ if not _client:
85
+ return []
86
+ qvec = embed_query(query)
87
+ qfilter = qmodels.Filter(must=[qmodels.FieldCondition(key="topic", match=qmodels.MatchValue(value=topic))]) if topic else None
88
+ res = _client.search(collection_name=_collection, query_vector=qvec, limit=topk, query_filter=qfilter)
89
+ return [{"score": r.score, "text": r.payload.get("text",""), "payload": r.payload} for r in res]
90
+
91
+ def web_search(query: str, n: int = 3, api_key: str | None = None):
92
+ key = api_key or os.getenv("TAVILY_API_KEY", "")
93
+ if not key:
94
+ return []
95
+ r = requests.post("https://api.tavily.com/search", json={
96
+ "api_key": key,
97
+ "query": query,
98
+ "search_depth": "basic",
99
+ "max_results": n
100
+ }, timeout=30)
101
+ data = r.json()
102
+ out = []
103
+ for item in data.get("results", [])[:n]:
104
+ text = (item.get("title","") + "\n" + item.get("content",""))[:1500]
105
+ out.append({"url": item.get("url"), "text": text})
106
+ return out
107
+
108
+ # ------------ Q&A without LLM (template-based) ------------
109
+ def answer_from_context(question: str, local_hits, web_snips):
110
+ parts = ["**Otázka:** " + question, "\n**Odpoveď (kompilované zo zdrojov):**"]
111
+ if local_hits:
112
+ parts.append("- Z lokálnych PDF vyplýva:")
113
+ for i,h in enumerate(local_hits):
114
+ snippet = h['text'][:400].strip().replace('\n',' ')
115
+ parts.append(f" [{i+1}] {snippet} … (score {h['score']:.2f})")
116
+ if web_snips:
117
+ parts.append("- Z webu:")
118
+ for j,w in enumerate(web_snips):
119
+ snippet = w['text'][:300].strip().replace('\n',' ')
120
+ parts.append(f" [W{j+1}] {snippet} … (zdroj: {w['url']})")
121
+ parts.append("\n**Poznámka:** Toto je súhrn citovaných pasáží. Pre presnejšiu odpoveď pridaj viac PDF alebo zapni lepší model neskôr.")
122
+ return "\n".join(parts)
123
+
124
+ # ------------ Gradio UI ------------
125
+ def on_connect(url, key, collection):
126
+ try:
127
+ connect_qdrant(url, key, collection)
128
+ return gr.update(value=f"✅ Pripojené k Qdrant: {collection}"), ""
129
+ except Exception as e:
130
+ return gr.update(value=f"❌ Chyba pripojenia"), str(e)
131
+
132
+ with gr.Blocks(title="EDM & Fanuc – Hybrid RAG (PDF + Web)") as demo:
133
+ gr.Markdown("# 🔧 EDM & Fanuc – Hybrid RAG (PDF + Web)")
134
+
135
+ with gr.Row():
136
+ with gr.Column(scale=1):
137
+ gr.Markdown("### Qdrant nastavenie")
138
+ q_url = gr.Textbox(label="QDRANT_URL", value=os.getenv("QDRANT_URL", ""))
139
+ q_key = gr.Textbox(label="QDRANT_API_KEY", value=os.getenv("QDRANT_API_KEY", ""), type="password")
140
+ collection_tb = gr.Textbox(label="Collection", value="docs")
141
+ connect_btn = gr.Button("🔌 Connect")
142
+ status = gr.Textbox(label="Status", interactive=False)
143
+ err = gr.Textbox(label="Error", interactive=False)
144
+
145
+ def _connect(u,k,c):
146
+ s,e = on_connect(u,k,c)
147
+ return s,e
148
+ connect_btn.click(_connect, inputs=[q_url,q_key,collection_tb], outputs=[status, err])
149
+
150
+ gr.Markdown("### Ingest PDF")
151
+ topic_dd = gr.Dropdown(["EDM","FANUC","general"], value="EDM", label="Topic")
152
+ chunk_chars = gr.Slider(800, 2500, value=1500, step=100, label="Chunk size")
153
+ overlap = gr.Slider(0, 400, value=200, step=20, label="Overlap")
154
+ upload = gr.File(file_count="multiple", file_types=[".pdf"], label="PDF súbory")
155
+ ingest_btn = gr.Button("📥 Indexovať do Qdrant")
156
+ ingest_out = gr.Textbox(label="Výstup")
157
+
158
+ def _ingest(files, topic, size, ov):
159
+ msg,_ = ingest_pdfs(files, topic, size, ov)
160
+ return msg
161
+ ingest_btn.click(_ingest, inputs=[upload, topic_dd, chunk_chars, overlap], outputs=ingest_out)
162
+
163
+ with gr.Column(scale=2):
164
+ gr.Markdown("### Chat (PDF + voliteľne web)")
165
+ question = gr.Textbox(label="Otázka", placeholder="Napr. G41 vs G42 na Fanuc…", lines=3)
166
+ topic_chat = gr.Dropdown(["EDM","FANUC","general"], value="EDM", label="Topic filter")
167
+ topk = gr.Slider(1, 8, value=5, step=1, label="Top‑K")
168
+ minscore = gr.Slider(0, 1, value=0.35, step=0.01, label="Min lokálne score pre vypnutie webu")
169
+ use_web = gr.Checkbox(value=True, label="Povoliť web doplnenie (Tavily)")
170
+ tavily_key = gr.Textbox(label="TAVILY_API_KEY (nepovinné)", value=os.getenv("TAVILY_API_KEY",""), type="password")
171
+ ask_btn = gr.Button("💬 Spýtať sa")
172
+ answer_md = gr.Markdown()
173
+
174
+ def _ask(q, topic, k, thr, web, tkey):
175
+ local_hits = search_local(q, int(k), topic)
176
+ good = any(h.get("score",0) >= float(thr) for h in local_hits)
177
+ web_snips = web_search(q, n=3, api_key=tkey) if (web and not good) else []
178
+ return answer_from_context(q, local_hits, web_snips)
179
+
180
+ ask_btn.click(_ask, inputs=[question, topic_chat, topk, minscore, use_web, tavily_key], outputs=answer_md)
181
+
182
+ if __name__ == "__main__":
183
+ demo.launch()
184
+
185
+