feat(thinking): toggle to enable/disable model thinking mode
Add a Thinking on/off button next to Auto-Play TTS. When OFF (default), the client sends enable_thinking=false with each chat_message and the server passes chat_template_kwargs.enable_thinking=false to self-hosted OpenAI-compatible endpoints (Qwen3-style), suppressing chain-of-thought to save tokens rather than merely hiding it. Skips the param for api.openai.com (which would 400) and o1/o3 (always think); templates that ignore the kwarg simply drop it. Client-side reasoning suppression remains as a fallback. State persists to localStorage across desktop and mobile.
This commit is contained in:
parent
e50fe6e830
commit
57a5b6f243
3 changed files with 79 additions and 19 deletions
49
app.py
49
app.py
|
|
@ -1899,7 +1899,14 @@ def handle_message(data):
|
|||
gevent.spawn(generate_dalle_image, room_name, message, username)
|
||||
else:
|
||||
# All other models (Groq, Together, Mistral, etc.) use OpenAI client
|
||||
gevent.spawn(chat_gpt, username, room_name, model_name=model)
|
||||
enable_thinking = data.get("enable_thinking", True)
|
||||
gevent.spawn(
|
||||
chat_gpt,
|
||||
username,
|
||||
room_name,
|
||||
model_name=model,
|
||||
enable_thinking=enable_thinking,
|
||||
)
|
||||
|
||||
|
||||
@socketio.on("delete_message")
|
||||
|
|
@ -2120,7 +2127,7 @@ def chat_claude(
|
|||
socketio.emit("delete_processing_message", msg_id, room=room_name)
|
||||
|
||||
|
||||
def chat_gpt(username, room_name, model_name="gpt-4o-mini"):
|
||||
def chat_gpt(username, room_name, model_name="gpt-4o-mini", enable_thinking=True):
|
||||
openai_client, model_name = get_openai_client_and_model(model_name)
|
||||
|
||||
temperature = 0
|
||||
|
|
@ -2194,23 +2201,29 @@ def chat_gpt(username, room_name, model_name="gpt-4o-mini"):
|
|||
|
||||
first_chunk = True
|
||||
|
||||
create_kwargs = {
|
||||
"model": model_name,
|
||||
"messages": chat_history,
|
||||
"n": 1,
|
||||
"stream": True,
|
||||
}
|
||||
if "o3" not in model_name:
|
||||
# o3 does not support temperature at all!
|
||||
create_kwargs["temperature"] = temperature
|
||||
if not enable_thinking:
|
||||
# Qwen3 / vLLM-style switch to suppress chain-of-thought and save
|
||||
# tokens. OpenAI's hosted API rejects unknown body params, so only
|
||||
# send to self-hosted OpenAI-compatible endpoints; o1/o3 think
|
||||
# unconditionally and would 400 on this anyway. Models whose chat
|
||||
# template ignores the kwarg (e.g. Hermes) simply drop it.
|
||||
base_url = str(getattr(openai_client, "base_url", "") or "")
|
||||
if "api.openai.com" not in base_url:
|
||||
create_kwargs["extra_body"] = {
|
||||
"chat_template_kwargs": {"enable_thinking": False}
|
||||
}
|
||||
|
||||
try:
|
||||
if "o3" in model_name:
|
||||
# o3 does not support temperature at all!
|
||||
chunks = openai_client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=chat_history,
|
||||
n=1,
|
||||
stream=True,
|
||||
)
|
||||
else:
|
||||
chunks = openai_client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=chat_history,
|
||||
n=1,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
)
|
||||
chunks = openai_client.chat.completions.create(**create_kwargs)
|
||||
except Exception as e:
|
||||
with app.app_context():
|
||||
message_content = f"{model_name} Error: {e}"
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@
|
|||
Auto-Play TTS: OFF
|
||||
</button>
|
||||
</div>
|
||||
<div id="show-thinking-mobile">
|
||||
<button id="show-thinking-btn-mobile" onclick="toggleShowThinking()" style="width: 100%; margin-top: 10px; background-color: #f44336; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer;">
|
||||
Thinking: OFF
|
||||
</button>
|
||||
</div>
|
||||
<div id="activity-controls-mobile">
|
||||
<h3>Activities</h3>
|
||||
<div id="current-activity-info-mobile" style="display: none;">
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@
|
|||
Auto-Play TTS: OFF
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="show-thinking-btn" onclick="toggleShowThinking()" style="width: 100%; margin-top: 10px; background-color: #f44336; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer;">
|
||||
Thinking: OFF
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="activity-controls">
|
||||
<h3>Activities</h3>
|
||||
|
|
@ -148,6 +153,13 @@ let autoPlayTTS = localStorage.getItem('autoPlayTTS') === 'true' || false;
|
|||
let ttsQueue = [];
|
||||
let isPlayingTTS = false;
|
||||
|
||||
// Thinking-mode state. Default OFF. When OFF we send enable_thinking=false
|
||||
// with each chat_message so the server asks the model (Qwen3-style) to skip
|
||||
// chain-of-thought entirely — saving tokens, not just hiding output. As a
|
||||
// fallback, any reasoning_content that still arrives is dropped client-side
|
||||
// (see message_chunk handler). Toggle persists to localStorage.
|
||||
let showThinking = localStorage.getItem('showThinking') === 'true';
|
||||
|
||||
// Vision model state for auto alt-text
|
||||
let visionAvailable = false;
|
||||
let visionModel = null;
|
||||
|
|
@ -375,6 +387,9 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
|||
// Initialize auto-play TTS button state from localStorage
|
||||
updateAutoPlayTTSDisplay();
|
||||
|
||||
// Initialize show-thinking button state from localStorage
|
||||
updateShowThinkingDisplay();
|
||||
|
||||
// Check for vision model availability (enables image hover descriptions)
|
||||
initVisionCapability();
|
||||
|
||||
|
|
@ -660,7 +675,8 @@ function sendMessage() {
|
|||
"username": username,
|
||||
"message": messageToSend,
|
||||
"model": model, // Pass model as a separate attribute
|
||||
"room_name": room_name
|
||||
"room_name": room_name,
|
||||
"enable_thinking": showThinking // false => ask model to skip chain-of-thought
|
||||
});
|
||||
messageTextarea.value = "";
|
||||
// Reset textarea height after sending
|
||||
|
|
@ -1112,6 +1128,29 @@ function processNextTTS() {
|
|||
});
|
||||
}
|
||||
|
||||
// Update show-thinking button labels/colors on both desktop + mobile.
|
||||
function updateShowThinkingDisplay() {
|
||||
const btn = document.getElementById("show-thinking-btn");
|
||||
const btnMobile = document.getElementById("show-thinking-btn-mobile");
|
||||
const label = showThinking ? "Thinking: ON" : "Thinking: OFF";
|
||||
const color = showThinking ? "#4CAF50" : "#f44336";
|
||||
if (btn) {
|
||||
btn.textContent = label;
|
||||
btn.style.backgroundColor = color;
|
||||
}
|
||||
if (btnMobile) {
|
||||
btnMobile.textContent = label;
|
||||
btnMobile.style.backgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle reasoning_content display. Off by default; persisted in localStorage.
|
||||
function toggleShowThinking() {
|
||||
showThinking = !showThinking;
|
||||
localStorage.setItem('showThinking', showThinking.toString());
|
||||
updateShowThinkingDisplay();
|
||||
}
|
||||
|
||||
// Function to update auto-play TTS button display
|
||||
function updateAutoPlayTTSDisplay() {
|
||||
const autoPlayBtn = document.getElementById("auto-play-tts-btn");
|
||||
|
|
@ -1448,7 +1487,10 @@ socket.on("message_chunk", (data) => {
|
|||
// the message-content div. Only appears if the server actually streams
|
||||
// delta.reasoning_content (i.e., thinking is on AND the model is using it).
|
||||
// Auto-collapses below on the first content delta.
|
||||
// Fallback guard: with Thinking OFF the server already suppresses reasoning
|
||||
// at the model, but if a model ignores the switch and streams anyway, drop it.
|
||||
if (data.reasoning_content) {
|
||||
if (!showThinking) return;
|
||||
let thinkingDetails = messageWrapper.querySelector(".message-thinking");
|
||||
if (!thinkingDetails) {
|
||||
// Ensure a message-body wrapper exists to anchor against
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue