zakerytclarke commited on
Commit
f1d79a3
·
verified ·
1 Parent(s): 1cff0af

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +78 -121
src/streamlit_app.py CHANGED
@@ -23,9 +23,6 @@ MAX_NEW_TOKENS = 192
23
  TOP_K_SEARCH = 3
24
  LOGO_URL = "https://teapotai.com/assets/logo.gif"
25
 
26
- # How many (user,assistant) pairs to include in the prompt by default
27
- MAX_TURNS_IN_PROMPT = 6
28
-
29
  st.set_page_config(page_title="TeapotAI Chat", page_icon="🫖", layout="centered")
30
 
31
 
@@ -68,14 +65,17 @@ if "needs_answer" not in st.session_state:
68
 
69
 
70
  # =========================
71
- # HEADER (prevent logo flash)
 
 
72
  # =========================
73
- col1, col2 = st.columns([1, 7], vertical_alignment="center")
74
- with col1:
75
- st.image(LOGO_URL, width=56)
76
- with col2:
77
- st.markdown("## TeapotAI Chat")
78
- st.caption("Grounded answers with web context")
 
79
 
80
 
81
  # =========================
@@ -102,14 +102,6 @@ with st.sidebar:
102
  placeholder="Extra context appended after web snippets…",
103
  )
104
 
105
- max_turns = st.slider(
106
- "Conversation turns in prompt",
107
- min_value=0,
108
- max_value=12,
109
- value=MAX_TURNS_IN_PROMPT,
110
- help="How many recent (user, assistant) pairs to include in the prompt.",
111
- )
112
-
113
 
114
  # =========================
115
  # WEB SEARCH (ALWAYS ON)
@@ -145,64 +137,28 @@ def web_search_snippets(query: str):
145
 
146
 
147
  # =========================
148
- # CONTEXT + PROMPT BUILDING
149
  # =========================
150
  def count_tokens(text: str) -> int:
151
  return len(tokenizer.encode(text)) if text else 0
152
 
153
 
154
- def build_conversation(messages, turns: int) -> str:
155
- """
156
- Build a compact transcript from the last `turns` (user,assistant) pairs.
157
- """
158
- if turns <= 0:
159
- return ""
160
-
161
- # Collect last 2*turns messages ending at the most recent message
162
- # Keep only user/assistant roles.
163
- filtered = [m for m in messages if m.get("role") in ("user", "assistant")]
164
-
165
- # Take tail, but ensure we start on a user message if possible
166
- tail = filtered[-(2 * turns) :]
167
- # If first is assistant, drop it (misaligned pair)
168
- if tail and tail[0]["role"] == "assistant":
169
- tail = tail[1:]
170
-
171
- lines = []
172
- for m in tail:
173
- role = "User" if m["role"] == "user" else "Assistant"
174
- content = (m.get("content") or "").strip()
175
- if content:
176
- lines.append(f"{role}: {content}")
177
- return "\n".join(lines).strip()
178
-
179
-
180
- def truncate_to_token_budget(full_prompt: str, max_tokens: int) -> str:
181
- ids = tokenizer.encode(full_prompt)
182
  if len(ids) <= max_tokens:
183
- return full_prompt
184
- # Tail truncate to keep the most recent instruction + question
185
- ids = ids[-max_tokens:]
186
  return tokenizer.decode(ids, skip_special_tokens=True)
187
 
188
 
189
- def build_prompt(web_ctx: str, local_ctx: str, system: str, convo: str, question: str) -> str:
190
- # Order matters: context first, then system, then convo, then question.
191
- parts = []
192
  ctx = f"{web_ctx}\n\n{local_ctx}".strip()
 
193
  if ctx:
194
- parts.append("Context:\n" + ctx)
195
-
196
- parts.append("System:\n" + system.strip())
197
-
198
- if convo:
199
- parts.append("Conversation:\n" + convo)
200
-
201
- parts.append("User:\n" + question.strip())
202
- parts.append("Assistant:\n") # encourages continuation style
203
-
204
  raw = "\n\n".join(parts).strip() + "\n"
205
- # Enforce input budget at token level
206
  return truncate_to_token_budget(raw, MAX_INPUT_TOKENS)
207
 
208
 
@@ -237,9 +193,38 @@ def handle_feedback(idx: int):
237
  val = st.session_state.get(f"fb_{idx}")
238
  st.session_state.messages[idx]["feedback"] = val
239
 
240
- # If you later add LangSmith run_ids per message, hook it here.
241
- # (Keeping this simple/stable like your previous version.)
242
- # if ls_client and st.session_state.messages[idx].get("run_id"): ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
 
245
  # =========================
@@ -250,32 +235,16 @@ for i, msg in enumerate(st.session_state.messages):
250
  st.markdown(msg["content"])
251
 
252
  if msg["role"] == "assistant":
253
- # Inline row: info popover + thumbs + metrics
254
- c_info, c_fb, c_metrics = st.columns([1.2, 1.4, 10], vertical_alignment="center")
255
-
256
- with c_info:
257
- with st.popover("ℹ️", help="Inspect prompt"):
258
- st.markdown("**Prompt sent to model**")
259
- st.code(msg.get("prompt", ""), language="text")
260
-
261
- with c_fb:
262
- key = f"fb_{i}"
263
- st.session_state.setdefault(key, msg.get("feedback"))
264
- st.feedback(
265
- "thumbs",
266
- key=key,
267
- disabled=msg.get("feedback") is not None,
268
- on_change=handle_feedback,
269
- args=(i,),
270
- )
271
-
272
- with c_metrics:
273
- st.caption(
274
- f"🔎 {msg['search_time']:.2f}s "
275
- f"• 🧠 {msg['gen_time']:.2f}s "
276
- f"• ⚡ {msg['tps']:.1f} tok/s "
277
- f"• 🧾 in {msg['input_tokens']} • out {msg['output_tokens']}"
278
- )
279
 
280
 
281
  # =========================
@@ -299,25 +268,16 @@ if (
299
  ):
300
  question = st.session_state.messages[-1]["content"]
301
 
302
- # Web search
303
  web_ctx, search_time = web_search_snippets(question)
304
 
305
- # Conversation transcript (from prior messages, excluding current user msg is fine either way;
306
- # keeping it includes the last user msg too, but we also add question explicitly.)
307
- convo = build_conversation(st.session_state.messages[:-1], turns=max_turns)
308
-
309
- # Prompt
310
  prompt = build_prompt(
311
  web_ctx=web_ctx,
312
  local_ctx=local_context,
313
  system=system_prompt,
314
- convo=convo,
315
  question=question,
316
  )
317
-
318
  input_tokens = count_tokens(prompt)
319
 
320
- # Stream normally
321
  with st.chat_message("assistant"):
322
  placeholder = st.empty()
323
  start = time.perf_counter()
@@ -331,27 +291,24 @@ if (
331
  output_tokens = count_tokens(final_text)
332
  tps = output_tokens / gen_time if gen_time > 0 else 0.0
333
 
334
- # Inline row under the live message
335
- c_info, c_fb, c_metrics = st.columns([1.2, 1.4, 10], vertical_alignment="center")
336
-
337
- with c_info:
338
- with st.popover("ℹ️", help="Inspect prompt"):
339
- st.markdown("**Prompt sent to model**")
340
- st.code(prompt, language="text")
341
-
342
- # For the live message, we don't have a saved index yet; show disabled thumbs placeholder
343
- with c_fb:
344
- st.feedback("thumbs", key="fb_live", disabled=True)
345
 
346
- with c_metrics:
347
- st.caption(
348
- f"🔎 {search_time:.2f}s "
349
- f"• 🧠 {gen_time:.2f}s "
350
- f"• ⚡ {tps:.1f} tok/s "
351
- f"• 🧾 in {input_tokens} • out {output_tokens}"
352
- )
353
 
354
- # Persist assistant message (so feedback attaches properly after rerun)
355
  st.session_state.messages.append(
356
  {
357
  "role": "assistant",
 
23
  TOP_K_SEARCH = 3
24
  LOGO_URL = "https://teapotai.com/assets/logo.gif"
25
 
 
 
 
26
  st.set_page_config(page_title="TeapotAI Chat", page_icon="🫖", layout="centered")
27
 
28
 
 
65
 
66
 
67
  # =========================
68
+ # HEADER (reduce logo flash)
69
+ # - fixed width prevents "giant image then shrink"
70
+ # - container keeps layout stable
71
  # =========================
72
+ with st.container():
73
+ col1, col2 = st.columns([1, 7], vertical_alignment="center")
74
+ with col1:
75
+ st.image(LOGO_URL, width=56) # fixed width = stable, no huge flash
76
+ with col2:
77
+ st.markdown("## TeapotAI Chat")
78
+ st.caption("Grounded answers with web context")
79
 
80
 
81
  # =========================
 
102
  placeholder="Extra context appended after web snippets…",
103
  )
104
 
 
 
 
 
 
 
 
 
105
 
106
  # =========================
107
  # WEB SEARCH (ALWAYS ON)
 
137
 
138
 
139
  # =========================
140
+ # PROMPT BUILDING (NO CONVERSATION HISTORY)
141
  # =========================
142
  def count_tokens(text: str) -> int:
143
  return len(tokenizer.encode(text)) if text else 0
144
 
145
 
146
+ def truncate_to_token_budget(text: str, max_tokens: int) -> str:
147
+ ids = tokenizer.encode(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  if len(ids) <= max_tokens:
149
+ return text
150
+ ids = ids[-max_tokens:] # tail truncate
 
151
  return tokenizer.decode(ids, skip_special_tokens=True)
152
 
153
 
154
+ def build_prompt(web_ctx: str, local_ctx: str, system: str, question: str) -> str:
 
 
155
  ctx = f"{web_ctx}\n\n{local_ctx}".strip()
156
+ parts = []
157
  if ctx:
158
+ parts.append(ctx)
159
+ parts.append(system.strip())
160
+ parts.append(question.strip())
 
 
 
 
 
 
 
161
  raw = "\n\n".join(parts).strip() + "\n"
 
162
  return truncate_to_token_budget(raw, MAX_INPUT_TOKENS)
163
 
164
 
 
193
  val = st.session_state.get(f"fb_{idx}")
194
  st.session_state.messages[idx]["feedback"] = val
195
 
196
+ # If you want to wire LangSmith feedback to a run later, store run_id per message and use it here.
197
+ # For now we keep it stable and local like the earlier version.
198
+
199
+
200
+ def render_inline_controls(msg: dict, feedback_key: str, feedback_disabled: bool, feedback_idx: int | None):
201
+ """
202
+ Inline row under assistant message:
203
+ ℹ️ popover (full prompt), thumbs, metrics
204
+ """
205
+ c_info, c_fb, c_metrics = st.columns([1.1, 1.7, 10], vertical_alignment="center")
206
+
207
+ with c_info:
208
+ with st.popover("ℹ️", help="Inspect"):
209
+ st.markdown("**Prompt (sent to model)**")
210
+ st.code(msg.get("prompt", ""), language="text")
211
+
212
+ with c_fb:
213
+ st.feedback(
214
+ "thumbs",
215
+ key=feedback_key,
216
+ disabled=feedback_disabled,
217
+ on_change=(handle_feedback if (not feedback_disabled and feedback_idx is not None) else None),
218
+ args=((feedback_idx,) if (not feedback_disabled and feedback_idx is not None) else None),
219
+ )
220
+
221
+ with c_metrics:
222
+ st.caption(
223
+ f"🔎 {msg.get('search_time', 0.0):.2f}s "
224
+ f"• 🧠 {msg.get('gen_time', 0.0):.2f}s "
225
+ f"• ⚡ {msg.get('tps', 0.0):.1f} tok/s "
226
+ f"• 🧾 in {msg.get('input_tokens', 0)} • out {msg.get('output_tokens', 0)}"
227
+ )
228
 
229
 
230
  # =========================
 
235
  st.markdown(msg["content"])
236
 
237
  if msg["role"] == "assistant":
238
+ # Ensure feedback state exists
239
+ k = f"fb_{i}"
240
+ st.session_state.setdefault(k, msg.get("feedback"))
241
+
242
+ render_inline_controls(
243
+ msg=msg,
244
+ feedback_key=k,
245
+ feedback_disabled=(msg.get("feedback") is not None),
246
+ feedback_idx=i,
247
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
 
250
  # =========================
 
268
  ):
269
  question = st.session_state.messages[-1]["content"]
270
 
 
271
  web_ctx, search_time = web_search_snippets(question)
272
 
 
 
 
 
 
273
  prompt = build_prompt(
274
  web_ctx=web_ctx,
275
  local_ctx=local_context,
276
  system=system_prompt,
 
277
  question=question,
278
  )
 
279
  input_tokens = count_tokens(prompt)
280
 
 
281
  with st.chat_message("assistant"):
282
  placeholder = st.empty()
283
  start = time.perf_counter()
 
291
  output_tokens = count_tokens(final_text)
292
  tps = output_tokens / gen_time if gen_time > 0 else 0.0
293
 
294
+ live_msg = {
295
+ "prompt": prompt,
296
+ "search_time": search_time,
297
+ "gen_time": gen_time,
298
+ "input_tokens": input_tokens,
299
+ "output_tokens": output_tokens,
300
+ "tps": tps,
301
+ }
 
 
 
302
 
303
+ # Inline controls for the live message (thumbs disabled until it’s saved)
304
+ render_inline_controls(
305
+ msg=live_msg,
306
+ feedback_key="fb_live",
307
+ feedback_disabled=True,
308
+ feedback_idx=None,
309
+ )
310
 
311
+ # Save assistant message so thumbs attach after rerun
312
  st.session_state.messages.append(
313
  {
314
  "role": "assistant",