A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 06:00 UTC
[DOC] Add reasoning capability to vLLM streamlit code (#19557)
Navanit Dubey committed
Jun 16, 2025 at 16:39 UTC
3e7506975c86a88ee3d490fdde182fb209bf1262
1 file changed
+165
-43
examples/online_serving/streamlit_openai_chatbot_webserver.py
+165
-43
@@ -11,6 +11,7 @@ Features:
11
- Streaming response display
12
- Configurable API endpoint
13
- Real-time chat history
14
+- Reasoning Display: Optional thinking process visualization
15
16
Requirements:
17
pip install streamlit openai
@@ -51,13 +52,33 @@ if "messages" not in st.session_state:
52
if "active_session" not in st.session_state:
53
st.session_state.active_session = None
54
55
+# Add new session state for reasoning
56
+if "show_reasoning" not in st.session_state:
57
+ st.session_state.show_reasoning = {}
58
+
59
# Initialize session state for API base URL
60
if "api_base_url" not in st.session_state:
61
st.session_state.api_base_url = openai_api_base
62
63
64
def create_new_chat_session():
60
- """Create a new chat session with timestamp as ID"""
65
+ """Create a new chat session with timestamp as unique identifier.
66
+
67
+ This function initializes a new chat session by:
68
+ 1. Generating a timestamp-based session ID
69
+ 2. Creating an empty message list for the new session
70
+ 3. Setting the new session as both current and active session
71
+ 4. Resetting the messages list for the new session
72
+
73
+ Returns:
74
+ None
75
+
76
+ Session State Updates:
77
+ - sessions: Adds new empty message list with timestamp key
78
+ - current_session: Sets to new session ID
79
+ - active_session: Sets to new session ID
80
+ - messages: Resets to empty list
81
+ """
82
session_id = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
83
st.session_state.sessions[session_id] = []
84
st.session_state.current_session = session_id
@@ -66,30 +87,98 @@ def create_new_chat_session():
87
88
89
def switch_to_chat_session(session_id):
69
- """Switch to a different chat session"""
90
+ """Switch the active chat context to a different session.
91
+
92
+ Args:
93
+ session_id (str): The timestamp ID of the session to switch to
94
+
95
+ This function handles chat session switching by:
96
+ 1. Setting the specified session as current
97
+ 2. Updating the active session marker
98
+ 3. Loading the messages history from the specified session
99
+
100
+ Session State Updates:
101
+ - current_session: Updated to specified session_id
102
+ - active_session: Updated to specified session_id
103
+ - messages: Loaded from sessions[session_id]
104
+ """
105
st.session_state.current_session = session_id
106
st.session_state.active_session = session_id
107
st.session_state.messages = st.session_state.sessions[session_id]
108
109
75
-def get_llm_response(messages, model):
76
- """Get streaming response from llm
110
+def get_llm_response(messages, model, reason, content_ph=None, reasoning_ph=None):
111
+ """Generate and stream LLM response with optional reasoning process.
112
113
Args:
79
- messages: List of message dictionaries
80
- model: Name of model
114
+ messages (list): List of conversation message dicts with 'role' and 'content'
115
+ model (str): The model identifier to use for generation
116
+ reason (bool): Whether to enable and display reasoning process
117
+ content_ph (streamlit.empty): Placeholder for streaming response content
118
+ reasoning_ph (streamlit.empty): Placeholder for streaming reasoning process
119
120
Returns:
83
- Streaming response object or error message string
121
+ tuple: (str, str)
122
+ - First string contains the complete response text
123
+ - Second string contains the complete reasoning text (if enabled)
124
+
125
+ Features:
126
+ - Streams both reasoning and response text in real-time
127
+ - Handles model API errors gracefully
128
+ - Supports live updating of thinking process
129
+ - Maintains separate content and reasoning displays
130
+
131
+ Raises:
132
+ Exception: Wrapped in error message if API call fails
133
+
134
+ Note:
135
+ The function uses streamlit placeholders for live updates.
136
+ When reason=True, the reasoning process appears above the response.
137
"""
138
+ full_text = ""
139
+ think_text = ""
140
+ live_think = None
141
+ # Build request parameters
142
+ params = {"model": model, "messages": messages, "stream": True}
143
+ if reason:
144
+ params["extra_body"] = {"chat_template_kwargs": {"enable_thinking": True}}
145
+
146
try:
86
- response = client.chat.completions.create(
87
- model=model, messages=messages, stream=True
88
- )
89
- return response
147
+ response = client.chat.completions.create(**params)
148
+ if isinstance(response, str):
149
+ if content_ph:
150
+ content_ph.markdown(response)
151
+ return response, ""
152
+
153
+ # Prepare reasoning expander above content
154
+ if reason and reasoning_ph:
155
+ exp = reasoning_ph.expander("💭 Thinking Process (live)", expanded=True)
156
+ live_think = exp.empty()
157
+
158
+ # Stream chunks
159
+ for chunk in response:
160
+ delta = chunk.choices[0].delta
161
+ # Stream reasoning first
162
+ if reason and hasattr(delta, "reasoning_content") and live_think:
163
+ rc = delta.reasoning_content
164
+ if rc:
165
+ think_text += rc
166
+ live_think.markdown(think_text + "▌")
167
+ # Then stream content
168
+ if hasattr(delta, "content") and delta.content and content_ph:
169
+ full_text += delta.content
170
+ content_ph.markdown(full_text + "▌")
171
+
172
+ # Finalize displays: reasoning remains above, content below
173
+ if reason and live_think:
174
+ live_think.markdown(think_text)
175
+ if content_ph:
176
+ content_ph.markdown(full_text)
177
+
178
+ return full_text, think_text
179
except Exception as e:
180
st.error(f"Error details: {str(e)}")
92
- return f"Error: {str(e)}"
181
+ return f"Error: {str(e)}", ""
182
183
184
# Sidebar - API Settings first
@@ -108,6 +197,7 @@ st.sidebar.title("Chat Sessions")
197
if st.sidebar.button("New Session"):
198
create_new_chat_session()
199
200
+
201
# Display all sessions in reverse chronological order
202
for session_id in sorted(st.session_state.sessions.keys(), reverse=True):
203
# Mark the active session with a pinned button
@@ -143,47 +233,79 @@ if st.session_state.current_session is None:
233
create_new_chat_session()
234
st.session_state.active_session = st.session_state.current_session
235
146
-# Display chat history for current session
147
-for message in st.session_state.messages:
148
- with st.chat_message(message["role"]):
149
- st.write(message["content"])
236
+# Update the chat history display section
237
+for idx, msg in enumerate(st.session_state.messages):
238
+ # Render user messages normally
239
+ if msg["role"] == "user":
240
+ with st.chat_message("user"):
241
+ st.write(msg["content"])
242
+ # Render assistant messages with reasoning above
243
+ else:
244
+ # If reasoning exists for this assistant message, show it above the content
245
+ if idx in st.session_state.show_reasoning:
246
+ with st.expander("💭 Thinking Process", expanded=False):
247
+ st.markdown(st.session_state.show_reasoning[idx])
248
+ with st.chat_message("assistant"):
249
+ st.write(msg["content"])
250
+
251
+
252
+# Setup & Cache reasoning support check
253
+@st.cache_data(show_spinner=False)
254
+def server_supports_reasoning():
255
+ """Check if the current model supports reasoning capability.
256
+
257
+ Returns:
258
+ bool: True if the model supports reasoning, False otherwise
259
+ """
260
+ resp = client.chat.completions.create(
261
+ model=model,
262
+ messages=[{"role": "user", "content": "Hi"}],
263
+ stream=False,
264
+ )
265
+ return hasattr(resp.choices[0].message, "reasoning_content") and bool(
266
+ resp.choices[0].message.reasoning_content
267
+ )
268
151
-# Handle user input and generate llm response
269
+
270
+# Check support
271
+supports_reasoning = server_supports_reasoning()
272
+
273
+# Add reasoning toggle in sidebar if supported
274
+reason = False # Default to False
275
+if supports_reasoning:
276
+ reason = st.sidebar.checkbox("Enable Reasoning", value=False)
277
+else:
278
+ st.sidebar.markdown(
279
+ "<span style='color:gray;'>Reasoning unavailable for this model.</span>",
280
+ unsafe_allow_html=True,
281
+ )
282
+ # reason remains False
283
+
284
+# Update the input handling section
285
if prompt := st.chat_input("Type your message here..."):
153
- # Save user message to session
286
+ # Save and display user message
287
st.session_state.messages.append({"role": "user", "content": prompt})
288
st.session_state.sessions[st.session_state.current_session] = (
289
st.session_state.messages
290
)
158
-
159
- # Display user message
291
with st.chat_message("user"):
292
st.write(prompt)
293
163
- # Prepare messages for llm
164
- messages_for_llm = [
294
+ # Prepare LLM messages
295
+ msgs = [
296
{"role": m["role"], "content": m["content"]} for m in st.session_state.messages
297
]
298
168
- # Generate and display llm response
299
+ # Stream assistant response
300
with st.chat_message("assistant"):
170
- message_placeholder = st.empty()
171
- full_response = ""
172
-
173
- # Get streaming response from llm
174
- response = get_llm_response(messages_for_llm, model)
175
- if isinstance(response, str):
176
- message_placeholder.markdown(response)
177
- full_response = response
178
- else:
179
- for chunk in response:
180
- if hasattr(chunk.choices[0].delta, "content"):
181
- content = chunk.choices[0].delta.content
182
- if content:
183
- full_response += content
184
- message_placeholder.markdown(full_response + "▌")
185
-
186
- message_placeholder.markdown(full_response)
187
-
188
- # Save llm response to session history
189
- st.session_state.messages.append({"role": "assistant", "content": full_response})
301
+ # Placeholders: reasoning above, content below
302
+ reason_ph = st.empty()
303
+ content_ph = st.empty()
304
+ full, think = get_llm_response(msgs, model, reason, content_ph, reason_ph)
305
+ # Determine index for this new assistant message
306
+ message_index = len(st.session_state.messages)
307
+ # Save assistant reply
308
+ st.session_state.messages.append({"role": "assistant", "content": full})
309
+ # Persist reasoning in session state if any
310
+ if reason and think:
311
+ st.session_state.show_reasoning[message_index] = think