lukedaca commited on
Commit
4e11685
·
verified ·
1 Parent(s): 5e5a3d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -56
app.py CHANGED
@@ -22,7 +22,6 @@ Pravidla pro tebe:
22
  4. Pamatuj si, co uživatel říkal v předchozích větách této konverzace.
23
  """.strip()
24
 
25
-
26
  st.set_page_config(page_title="AI Rádce s pamětí", layout="centered")
27
  st.title("🧠 Chytrý Chatbot (s pamětí)")
28
 
@@ -32,35 +31,16 @@ MODEL_REPO = "QuantFactory/Meta-Llama-3-8B-Instruct-GGUF"
32
  MODEL_FILE = "Meta-Llama-3-8B-Instruct.Q4_K_M.gguf"
33
 
34
 
35
- with st.sidebar:
36
- st.header("Nastavení")
37
-
38
- urls_text = st.text_area(
39
- "URL zdroje (1 URL na řádek)",
40
- value="\n".join(DEFAULT_URLS),
41
- height=110,
42
- )
43
- urls = [u.strip() for u in urls_text.splitlines() if u.strip()]
44
-
45
- max_new_tokens = st.slider("Max nových tokenů (rychlost)", 32, 256, 128, 16)
46
- context_window = st.select_slider("Context window", options=[1024, 2048, 3072, 4096], value=2048)
47
-
48
- cpu_cnt = os.cpu_count() or 2
49
- threads = st.slider("Počet vláken (threads)", 1, min(8, cpu_cnt), min(4, cpu_cnt), 1)
50
- batch = st.select_slider("Batch", options=[64, 128, 256, 512], value=256)
51
-
52
- if st.button("🧹 Resetovat konverzaci"):
53
- st.session_state.pop("messages", None)
54
- st.session_state.pop("chat_engine", None)
55
- st.rerun()
56
 
57
 
58
  def create_llm(model_path: str, ctx_win: int, max_tok: int, n_threads: int, n_batch: int) -> LlamaCPP:
59
  """
60
- Kompatibilní konstrukce LlamaCPP napříč verzemi llama-index.
61
- Některé verze nepřijímají n_threads/n_batch přímo, ale jen přes model_kwargs.
62
  """
63
- # 1) zkusit přímé parametry (novější/verze dle wrapperu)
64
  try:
65
  return LlamaCPP(
66
  model_path=model_path,
@@ -74,7 +54,7 @@ def create_llm(model_path: str, ctx_win: int, max_tok: int, n_threads: int, n_ba
74
  except TypeError:
75
  pass
76
 
77
- # 2) fallback přes model_kwargs (časté u LlamaIndex wrapperu)
78
  try:
79
  return LlamaCPP(
80
  model_path=model_path,
@@ -85,7 +65,7 @@ def create_llm(model_path: str, ctx_win: int, max_tok: int, n_threads: int, n_ba
85
  verbose=False,
86
  )
87
  except TypeError:
88
- # 3) poslední fallback jen threads (někdy n_batch není podporovaný)
89
  return LlamaCPP(
90
  model_path=model_path,
91
  temperature=0.1,
@@ -97,42 +77,97 @@ def create_llm(model_path: str, ctx_win: int, max_tok: int, n_threads: int, n_ba
97
 
98
 
99
  @st.cache_resource
100
- def load_index_and_settings(urls_tuple: tuple[str, ...], ctx_win: int, max_tok: int, n_threads: int, n_batch: int) -> VectorStoreIndex:
101
- # stáhnout GGUF do HF cache
102
- model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
103
 
104
- llm = create_llm(model_path, ctx_win, max_tok, n_threads, n_batch)
105
 
106
- Settings.llm = llm
 
 
107
  Settings.embed_model = FastEmbedEmbedding(model_name="BAAI/bge-small-en-v1.5")
108
 
109
  docs = SimpleWebPageReader(html_to_text=True).load_data(list(urls_tuple))
110
  return VectorStoreIndex.from_documents(docs)
111
 
112
 
113
- def make_chat_engine() -> object:
114
- index = load_index_and_settings(tuple(urls), context_window, max_new_tokens, threads, batch)
 
 
 
 
 
 
115
 
116
- # paměť per-session (NEcacheovat)
117
- memory = ChatMemoryBuffer.from_defaults(token_limit=min(3000, context_window))
118
 
 
 
 
119
  return index.as_chat_engine(
120
- chat_mode="context",
121
  memory=memory,
122
  system_prompt=SYSTEM_PROMPT,
123
  verbose=False,
124
  )
125
 
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  if "chat_engine" not in st.session_state:
128
- with st.spinner("Startuji mozek bota... (načítám model a web)"):
129
  try:
130
- st.session_state.chat_engine = make_chat_engine()
131
  except Exception as e:
132
  st.error(f"Chyba při inicializaci: {e}")
133
  st.stop()
134
 
135
-
136
  if "messages" not in st.session_state:
137
  st.session_state.messages = []
138
 
@@ -141,6 +176,47 @@ for msg in st.session_state.messages:
141
  st.markdown(msg["content"])
142
 
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  prompt = st.chat_input("Zeptej se (např: Co umíš?)...")
145
  if prompt:
146
  st.session_state.messages.append({"role": "user", "content": prompt})
@@ -149,27 +225,25 @@ if prompt:
149
 
150
  with st.chat_message("assistant"):
151
  placeholder = st.empty()
152
- full = ""
153
- t0 = time.time()
154
 
155
- with st.spinner("Přemýšlím..."):
156
- try:
157
- stream = st.session_state.chat_engine.stream_chat(prompt)
158
- for chunk in stream.response_gen:
159
- full += chunk
160
- placeholder.markdown(full)
161
 
162
- if not full.strip():
163
- full = getattr(stream, "response", None) or "Nedostal jsem žádná data k odpovědi."
164
- placeholder.markdown(full)
 
 
 
165
 
166
- except Exception as e:
167
- full = f"Chyba při generování odpovědi: {e}"
168
- placeholder.markdown(full)
169
 
170
- st.caption(f"Hotovo za {time.time() - t0:.1f}s")
 
 
171
 
172
- st.session_state.messages.append({"role": "assistant", "content": full})
173
 
174
 
175
 
 
22
  4. Pamatuj si, co uživatel říkal v předchozích větách této konverzace.
23
  """.strip()
24
 
 
25
  st.set_page_config(page_title="AI Rádce s pamětí", layout="centered")
26
  st.title("🧠 Chytrý Chatbot (s pamětí)")
27
 
 
31
  MODEL_FILE = "Meta-Llama-3-8B-Instruct.Q4_K_M.gguf"
32
 
33
 
34
+ def _clamp(v: int, lo: int, hi: int) -> int:
35
+ return max(lo, min(hi, v))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
 
38
  def create_llm(model_path: str, ctx_win: int, max_tok: int, n_threads: int, n_batch: int) -> LlamaCPP:
39
  """
40
+ Kompatibilní konstrukce napříč verzemi llama-index.
41
+ Někdy wrapper nepřijme n_threads/n_batch přímo => použijeme model_kwargs.
42
  """
43
+ # 1) zkus přímé parametry
44
  try:
45
  return LlamaCPP(
46
  model_path=model_path,
 
54
  except TypeError:
55
  pass
56
 
57
+ # 2) fallback přes model_kwargs
58
  try:
59
  return LlamaCPP(
60
  model_path=model_path,
 
65
  verbose=False,
66
  )
67
  except TypeError:
68
+ # 3) poslední fallback: jen threads
69
  return LlamaCPP(
70
  model_path=model_path,
71
  temperature=0.1,
 
77
 
78
 
79
  @st.cache_resource
80
+ def get_model_path() -> str:
81
+ return hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
 
82
 
 
83
 
84
+ @st.cache_resource
85
+ def build_index(urls_tuple: tuple[str, ...]) -> VectorStoreIndex:
86
+ # Embed model: bez torch/cuda (rychlejší instalace a stabilní na HF)
87
  Settings.embed_model = FastEmbedEmbedding(model_name="BAAI/bge-small-en-v1.5")
88
 
89
  docs = SimpleWebPageReader(html_to_text=True).load_data(list(urls_tuple))
90
  return VectorStoreIndex.from_documents(docs)
91
 
92
 
93
+ @st.cache_resource
94
+ def load_llm_cached(ctx_win: int, max_tok: int, n_threads: int, n_batch: int) -> LlamaCPP:
95
+ model_path = get_model_path()
96
+ return create_llm(model_path, ctx_win, max_tok, n_threads, n_batch)
97
+
98
+
99
+ def make_chat_engine(urls_list: list[str], ctx_win: int, max_tok: int, n_threads: int, n_batch: int) -> object:
100
+ index = build_index(tuple(urls_list))
101
 
102
+ llm = load_llm_cached(ctx_win, max_tok, n_threads, n_batch)
103
+ Settings.llm = llm # nastavíme aktivní LLM pro LlamaIndex
104
 
105
+ memory = ChatMemoryBuffer.from_defaults(token_limit=min(1500, ctx_win))
106
+
107
+ # condense_plus_context bývá svižnější / stabilnější než čisté "context"
108
  return index.as_chat_engine(
109
+ chat_mode="condense_plus_context",
110
  memory=memory,
111
  system_prompt=SYSTEM_PROMPT,
112
  verbose=False,
113
  )
114
 
115
 
116
+ with st.sidebar:
117
+ st.header("Nastavení")
118
+
119
+ urls_text = st.text_area(
120
+ "URL zdroje (1 URL na řádek)",
121
+ value="\n".join(DEFAULT_URLS),
122
+ height=110,
123
+ )
124
+ urls = [u.strip() for u in urls_text.splitlines() if u.strip()]
125
+
126
+ safe_mode = st.toggle("Safe Mode (doporučeno pro HF CPU)", value=True)
127
+ st.caption("Safe Mode brání nastavení, které na HF CPU typicky 'zamrzne'.")
128
+
129
+ # Uživatelské vstupy
130
+ user_max_new_tokens = st.slider("Max nových tokenů (rychlost)", 32, 256, 96, 16)
131
+ user_context_window = st.select_slider("Context window", options=[1024, 2048, 3072, 4096], value=2048)
132
+
133
+ cpu_cnt = os.cpu_count() or 2
134
+ user_threads = st.slider("Počet vláken (threads)", 1, min(8, cpu_cnt), min(4, cpu_cnt), 1)
135
+ user_batch = st.select_slider("Batch", options=[64, 128, 256, 512], value=128)
136
+
137
+ if st.button("🧹 Resetovat konverzaci"):
138
+ st.session_state.pop("messages", None)
139
+ st.session_state.pop("chat_engine", None)
140
+ st.rerun()
141
+
142
+ # Tvrdé limity (aby se to nezabilo na HF CPU)
143
+ if safe_mode:
144
+ max_new_tokens = _clamp(user_max_new_tokens, 32, 128)
145
+ context_window = _clamp(user_context_window, 1024, 2048)
146
+ threads = _clamp(user_threads, 1, 4)
147
+ batch = _clamp(user_batch, 64, 256)
148
+ else:
149
+ max_new_tokens = user_max_new_tokens
150
+ context_window = user_context_window
151
+ threads = user_threads
152
+ batch = user_batch
153
+
154
+ st.sidebar.markdown("---")
155
+ st.sidebar.write("Aktivní parametry:")
156
+ st.sidebar.code(
157
+ f"max_new_tokens={max_new_tokens}\ncontext_window={context_window}\nthreads={threads}\nbatch={batch}"
158
+ )
159
+
160
+
161
+ # Inicializace enginu
162
  if "chat_engine" not in st.session_state:
163
+ with st.spinner("Startuji mozek bota... (model + index)"):
164
  try:
165
+ st.session_state.chat_engine = make_chat_engine(urls, context_window, max_new_tokens, threads, batch)
166
  except Exception as e:
167
  st.error(f"Chyba při inicializaci: {e}")
168
  st.stop()
169
 
170
+ # Historie zpráv
171
  if "messages" not in st.session_state:
172
  st.session_state.messages = []
173
 
 
176
  st.markdown(msg["content"])
177
 
178
 
179
+ def generate_answer(prompt: str) -> str:
180
+ """
181
+ Robustní generace odpovědi:
182
+ - zkusíme stream_chat (když funguje)
183
+ - pokud do 3s nepřiteče žádný chunk, fallback na chat()
184
+ """
185
+ engine = st.session_state.chat_engine
186
+
187
+ # 1) Stream pokus
188
+ try:
189
+ stream = engine.stream_chat(prompt)
190
+ full = ""
191
+ started = time.time()
192
+
193
+ # Některé verze blokují; proto "čekáme na první chunk" max 3s
194
+ got_any = False
195
+ for chunk in stream.response_gen:
196
+ got_any = True
197
+ full += chunk
198
+ yield ("stream", full) # průběžně vracíme text
199
+ # když se to rozjede, necháme to dojet normálně
200
+
201
+ if got_any and full.strip():
202
+ return # výstup už byl odeslán přes yield
203
+
204
+ # pokud nic nepřišlo, padneme do fallbacku
205
+ if time.time() - started < 3.0:
206
+ # malá pauza, ať se neflushuje zbytečně
207
+ time.sleep(0.2)
208
+
209
+ except Exception:
210
+ # stream nemusí být podporovaný/kompatibilní
211
+ pass
212
+
213
+ # 2) Fallback: klasický chat() (blokuje, ale aspoň funguje vždy)
214
+ resp = engine.chat(prompt)
215
+ answer = getattr(resp, "response", None) or str(resp)
216
+ yield ("final", answer)
217
+
218
+
219
+ # Chat input
220
  prompt = st.chat_input("Zeptej se (např: Co umíš?)...")
221
  if prompt:
222
  st.session_state.messages.append({"role": "user", "content": prompt})
 
225
 
226
  with st.chat_message("assistant"):
227
  placeholder = st.empty()
228
+ status = st.empty()
 
229
 
230
+ full_text = ""
231
+ t0 = time.time()
 
 
 
 
232
 
233
+ try:
234
+ # průběžné vykreslování
235
+ for kind, text in generate_answer(prompt):
236
+ full_text = text
237
+ placeholder.markdown(full_text)
238
+ status.caption(f"Generuji... {time.time() - t0:.1f}s")
239
 
240
+ status.caption(f"Hotovo za {time.time() - t0:.1f}s")
 
 
241
 
242
+ except Exception as e:
243
+ full_text = f"Chyba při generování odpovědi: {e}"
244
+ placeholder.markdown(full_text)
245
 
246
+ st.session_state.messages.append({"role": "assistant", "content": full_text})
247
 
248
 
249