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

Update app.py

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