From ba3280280df85ac5dbfa6e08b670d861c1ad0f6d Mon Sep 17 00:00:00 2001 From: "Samuele E. Locatelli" Date: Thu, 21 Aug 2025 17:07:29 +0000 Subject: [PATCH] Add cache for streamlit --- backend/main.py | 2 +- streamlit/app.py | 176 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 127 insertions(+), 51 deletions(-) diff --git a/backend/main.py b/backend/main.py index 2b6a0d7..1e19da6 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,7 +6,7 @@ import redis import json import httpx -r = redis.Redis(host='localhost', port=6379, db=0) +r = redis.Redis(host='localhost', port=6379, db=1) def save_chat(user_id, message): r.rpush(f"chat:{user_id}", json.dumps(message)) diff --git a/streamlit/app.py b/streamlit/app.py index 92b584a..d7ea2ce 100644 --- a/streamlit/app.py +++ b/streamlit/app.py @@ -1,12 +1,9 @@ -#### -#### Streamlit Streaming using LM Studio + Redis for multi-user context -#### run with: streamlit run app.py -#### Requires: pip install pypdf langchain langchain_openai redis - import streamlit as st +import streamlit.components.v1 as components import redis import json import uuid +import hashlib from langchain_core.messages import AIMessage, HumanMessage from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser @@ -15,92 +12,171 @@ from langchain_core.prompts import ChatPromptTemplate # --------------------- # Redis connection # --------------------- -# Adjust host/port as needed -r = redis.Redis(host="localhost", port=6379, db=1, decode_responses=True) +r = redis.Redis(host="localhost", port=6379, decode_responses=True) # --------------------- -# Helpers for session & history +# Session ID helpers # --------------------- -def get_session_id(): - """Ensure each user gets a unique, persistent session ID.""" - if "session_id" not in st.session_state: - st.session_state.session_id = str(uuid.uuid4()) - return st.session_state.session_id +def get_or_set_session_id(): + """Get or set session_id from browser localStorage and trigger rerun when first set.""" + if "session_id" in st.session_state: + return st.session_state.session_id + components.html(f""" + + """, height=0) + + sid = st.text_input("session_id_input", label_visibility="collapsed") + if sid and sid != st.session_state.get("session_id"): + st.session_state.session_id = sid + st.rerun() # immediately rerun with the new ID + return st.session_state.get("session_id") + +# --------------------- +# Chat history persistence +# --------------------- def load_history(session_id): - """Retrieve chat history for a given session from Redis.""" raw = r.get(f"chatbot:history:{session_id}") if raw: messages = json.loads(raw) - return [AIMessage(content=m["content"]) if m["type"] == "ai" - else HumanMessage(content=m["content"]) - for m in messages] - return [AIMessage(content="Hello, I am EgalWare's current ChatBot. How can I help you? (puoi fare domande in italiano, ma in inglese funziona meglio...)")] + return [ + AIMessage(content=m["content"]) if m["type"] == "ai" + else HumanMessage(content=m["content"]) + for m in messages + ] + return [AIMessage(content="Hello, I am EgalWare's ChatBot. How can I help you? (puoi fare domande in italiano, ma in inglese funziona meglio...)")] def save_history(session_id, history): - """Save chat history back to Redis.""" - messages = [] - for m in history: - messages.append({"type": "ai" if isinstance(m, AIMessage) else "human", "content": m.content}) + messages = [ + {"type": "ai" if isinstance(m, AIMessage) else "human", "content": m.content} + for m in history + ] r.set(f"chatbot:history:{session_id}", json.dumps(messages)) - # Optional TTL so old sessions expire: - r.expire(f"chatbot:history:{session_id}", 60 * 60 * 24 * 7) # 7 days + r.expire(f"chatbot:history:{session_id}", 60*60*24*7) + +def delete_history(session_id): + r.delete(f"chatbot:history:{session_id}") + for key in r.scan_iter(f"chatbot:cache:{session_id}:*"): + r.delete(key) # --------------------- -# LLM interaction +# Caching # --------------------- -def get_response(user_query, chat_history): - template = """ - You are a helpful assistant. Answer the following questions considering the history of the conversation: +def get_cache_key(session_id, prompt): + digest = hashlib.sha256(prompt.encode()).hexdigest() + return f"chatbot:cache:{session_id}:{digest}" - Chat history: {chat_history} +def get_cached_response(session_id, prompt): + return r.get(get_cache_key(session_id, prompt)) - User question: {user_question} - """ - prompt = ChatPromptTemplate.from_template(template) +def set_cached_response(session_id, prompt, response): + r.setex(get_cache_key(session_id, prompt), 300, response) +# --------------------- +# LLM +# --------------------- +def get_response(session_id, user_query, chat_history): + cached = get_cached_response(session_id, user_query) + if cached: + yield cached + return + + prompt = ChatPromptTemplate.from_template( + "You are a helpful assistant. Answer the following considering the history:\n\n" + "Chat history: {chat_history}\n\nUser question: {user_question}" + ) llm = ChatOpenAI( base_url="http://10.74.83.100:1234/v1", api_key="lm-studio", model="qwen/qwen3-4b-2507" ) - chain = prompt | llm | StrOutputParser() - return chain.stream({ + full_resp = "" + for chunk in chain.stream({ "chat_history": chat_history, - "user_question": user_query, - }) + "user_question": user_query + }): + full_resp += chunk + yield chunk + + set_cached_response(session_id, user_query, full_resp) # --------------------- -# Streamlit app setup +# UI # --------------------- st.set_page_config(page_title="Egalware Chatbot", page_icon="🤖") -st.title("Egalware's Chatbot") +session_id = get_or_set_session_id() -# Identify user & load history from Redis -session_id = get_session_id() -st.session_state.chat_history = load_history(session_id) +# Sticky header CSS +st.markdown(""" + +""", unsafe_allow_html=True) -# Render conversation +# Header +st.markdown('', unsafe_allow_html=True) + +# If still no ID, display placeholder history so UI doesn't look empty +if not session_id: + if "chat_history" not in st.session_state: + st.session_state.chat_history = [AIMessage(content="Initializing session… please wait")] +else: + if "chat_history" not in st.session_state: + st.session_state.chat_history = load_history(session_id) + +# Conversation display for message in st.session_state.chat_history: role = "AI" if isinstance(message, AIMessage) else "Human" with st.chat_message(role): st.write(message.content) -# Handle new input -user_query = st.chat_input("Type your message here...") -if user_query: +# Input +user_query = st.chat_input("Type your message here…") +if session_id and user_query: st.session_state.chat_history.append(HumanMessage(content=user_query)) - with st.chat_message("Human"): st.markdown(user_query) - with st.chat_message("AI"): - response_text = st.write_stream(get_response(user_query, st.session_state.chat_history)) + response_text = st.write_stream( + get_response(session_id, user_query, st.session_state.chat_history) + ) st.session_state.chat_history.append(AIMessage(content=response_text)) - - # Save updated history to Redis save_history(session_id, st.session_state.chat_history)