Skip to content

Commit 59e0419

Browse files
committed
feat: Respect reasoning context settings during KV cache pre-encoding
1 parent 710d52a commit 59e0419

7 files changed

Lines changed: 198 additions & 173 deletions

File tree

tools/server/public/bundle.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/server/public/bundle.js

Lines changed: 155 additions & 150 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/server/public/index.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,15 @@
1010
<meta charset="utf-8" />
1111
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjU2IiBpZD0ic2NyZWVuc2hvdC1lZjk0ZmJiMC1kYmFiLTgwZWQtODAwNi04OTQyOTkwMGVkYmYiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgZmlsbD0ibm9uZSIgdmVyc2lvbj0iMS4xIj48ZyBpZD0ic2hhcGUtZWY5NGZiYjAtZGJhYi04MGVkLTgwMDYtODk0Mjk5MDBlZGJmIiByeD0iMCIgcnk9IjAiPjxnIGlkPSJzaGFwZS1lZjk0ZmJiMC1kYmFiLTgwZWQtODAwNi04OTQyMTU3NTVjM2EiPjxnIGNsYXNzPSJmaWxscyIgaWQ9ImZpbGxzLWVmOTRmYmIwLWRiYWItODBlZC04MDA2LTg5NDIxNTc1NWMzYSI+PHJlY3Qgcng9IjAiIHJ5PSIwIiB4PSIwIiB5PSIwIiB0cmFuc2Zvcm09Im1hdHJpeCgxLjAwMDAwMCwgMC4wMDAwMDAsIDAuMDAwMDAwLCAxLjAwMDAwMCwgMC4wMDAwMDAsIDAuMDAwMDAwKSIgd2lkdGg9IjI1NiIgaGVpZ2h0PSIyNTYiIHN0eWxlPSJmaWxsOiByZ2IoMjcsIDMxLCAzMik7IGZpbGwtb3BhY2l0eTogMTsiLz48L2c+PC9nPjxnIGlkPSJzaGFwZS1lZjk0ZmJiMC1kYmFiLTgwZWQtODAwNi04OTQyMjM2M2VmM2YiIHJ4PSIwIiByeT0iMCI+PGcgaWQ9InNoYXBlLWVmOTRmYmIwLWRiYWItODBlZC04MDA2LTg5NDIyMzYzZWY0MCI+PGcgY2xhc3M9ImZpbGxzIiBpZD0iZmlsbHMtZWY5NGZiYjAtZGJhYi04MGVkLTgwMDYtODk0MjIzNjNlZjQwIj48cGF0aCBkPSJNMTcxLjY2NTAwODU0NDkyMTg4LDk5LjUzMDI1MDU0OTMxNjRMMTU5Ljc5OTUzMDAyOTI5Njg4LDEyMC42MjQ2ODcxOTQ4MjQyMkMxNDQuMTU0NTEwNDk4MDQ2ODgsMTA4LjU4MzI5MDEwMDA5NzY2LDEyMC45NTA0MTY1NjQ5NDE0LDEwNi44MjU0MTY1NjQ5NDE0LDEwNS4zMDUzOTcwMzM2OTE0LDExOS43NDU3NTA0MjcyNDYxQzgwLjA3OTgxMTA5NjE5MTQsMTQwLjU3NjUyMjgyNzE0ODQ0LDgxLjgzNzYyMzU5NjE5MTQsMTg4Ljc0MjI2Mzc5Mzk0NTMsMTIxLjEyNjE5NzgxNDk0MTQsMTg5LjAwNTg3NDYzMzc4OTA2QzEzMi4xMTMwMDY1OTE3OTY4OCwxODkuMDA1ODc0NjMzNzg5MDYsMTQxLjQyOTY1Njk4MjQyMTg4LDE4My44MjAxMTQxMzU3NDIyLDE1MS40NDk2NzY1MTM2NzE4OCwxODAuMzkyMzQ5MjQzMTY0MDZMMTU2LjcyMzM1ODE1NDI5Njg4LDIwMS4zOTg4NDk0ODczMDQ3QzE0Ny44NDU5MTY3NDgwNDY4OCwyMDUuNTI5ODkxOTY3NzczNDQsMTM4Ljc5MjkzODIzMjQyMTg4LDIwOS43NDg3MzM1MjA1MDc4LDEyOS4wMzY4MzQ3MTY3OTY4OCwyMTEuMDY3MTIzNDEzMDg1OTRDNDAuMDg4MzUyMjAzMzY5MTQsMjIzLjE5NjQ1NjkwOTE3OTcsNDUuMTg2MDA4NDUzMzY5MTQsOTQuNzg0MDA0MjExNDI1NzgsMTI1LjYwODg2MzgzMDU2NjQsODguMTA0MDcyNTcwODAwNzhDMTQyLjQ4NDM0NDQ4MjQyMTg4LDg2LjY5NzgyMjU3MDgwMDc4LDE1Ny4zMzgzNDgzODg2NzE4OCw5MS4wOTI0NzU4OTExMTMyOCwxNzEuNzUzMTQzMzEwNTQ2ODgsOTkuNTMwMjUwNTQ5MzE2NFoiIGNsYXNzPSJzdDAiIHN0eWxlPSJmaWxsOiByZ2IoMjU1LCAxMzAsIDU0KTsgZmlsbC1vcGFjaXR5OiAxOyIvPjwvZz48L2c+PGcgaWQ9InNoYXBlLWVmOTRmYmIwLWRiYWItODBlZC04MDA2LTg5NDIyMzYzZWY0MSI+PGcgY2xhc3M9ImZpbGxzIiBpZD0iZmlsbHMtZWY5NGZiYjAtZGJhYi04MGVkLTgwMDYtODk0MjIzNjNlZjQxIj48cGF0aCBkPSJNMTEwLjIyNzI3MjAzMzY5MTQsNzkuMzE0NzA0ODk1MDE5NTNDOTYuNjkxODcxNjQzMDY2NCw4My4zNTc4NTY3NTA0ODgyOCw4NC4xMjMyNjgxMjc0NDE0LDkwLjgyODgzNDUzMzY5MTQsNzQuNjMwNTkyMzQ2MTkxNCwxMDEuMjg4MTI0MDg0NDcyNjZDNzIuODcyNzc5ODQ2MTkxNCw4MC4wMTc4Mjk4OTUwMTk1Myw3Ny42MTg4NzM1OTYxOTE0LDM3LjAzNzkzNzE2NDMwNjY0LDEwMS4yNjIxODQxNDMwNjY0LDI4LjYwMDEwMzM3ODI5NTlDMTA0Ljc3ODA1MzI4MzY5MTQsMjcuMzY5NjQ5ODg3MDg0OTYsMTE2LjgxOTU1NzE4OTk0MTQsMjQuMjkzMzcxMjAwNTYxNTIzLDExNi40Njc5OTQ2ODk5NDE0LDMwLjUzMzc4ODY4MTAzMDI3M0MxMTYuMTE2MTg4MDQ5MzE2NCwzNi43NzQyNjUyODkzMDY2NCwxMDcuNzY2MzM0NTMzNjkxNCw0Ny40OTcyMjY3MTUwODc4OSwxMDUuNzQ1MDk0Mjk5MzE2NCw1My4yOTgyMzY4NDY5MjM4M0MxMDIuMjI5MjI1MTU4NjkxNCw2My40OTM4Njk3ODE0OTQxNCwxMDUuNDgxMTc4MjgzNjkxNCw3MC41MjUzNTI0NzgwMjczNCwxMTAuMzE1NDA2Nzk5MzE2NCw3OS40MDI2NTY1NTUxNzU3OFoiIGNsYXNzPSJzdDAiIHN0eWxlPSJmaWxsOiByZ2IoMjU1LCAxMzAsIDU0KTsgZmlsbC1vcGFjaXR5OiAxOyIvPjwvZz48L2c+PGcgaWQ9InNoYXBlLWVmOTRmYmIwLWRiYWItODBlZC04MDA2LTg5NDIyMzYzZWY0MiI+PGcgY2xhc3M9ImZpbGxzIiBpZD0iZmlsbHMtZWY5NGZiYjAtZGJhYi04MGVkLTgwMDYtODk0MjIzNjNlZjQyIj48cGF0aCBkPSJNMTQzLjYyNjkyMjYwNzQyMTg4LDEyNy42NTYyMTE4NTMwMjczNEwxNDMuNjI2OTIyNjA3NDIxODgsMTQzLjQ3NzA2NjA0MDAzOTA2TDE1Ny42ODk5MTA4ODg2NzE4OCwxNDMuNDc3MDY2MDQwMDM5MDZMMTU3LjY4OTkxMDg4ODY3MTg4LDE1NS43ODIxODA3ODYxMzI4TDE0My42MjY5MjI2MDc0MjE4OCwxNTUuNzgyMTgwNzg2MTMyOEwxNDMuNjI2OTIyNjA3NDIxODgsMTcwLjcyNDA3NTMxNzM4MjhMMTMwLjQ0Mjg0MDU3NjE3MTg4LDE3MC43MjQwNzUzMTczODI4TDEzMC40NDI4NDA1NzYxNzE4OCwxNTUuNzgyMTgwNzg2MTMyOEwxMTUuNTAwOTUzNjc0MzE2NCwxNTUuNzgyMTgwNzg2MTMyOEwxMTUuNTAwOTUzNjc0MzE2NCwxNDMuNDc3MDY2MDQwMDM5MDZMMTI5LjEyNDQ4MTIwMTE3MTg4LDE0My40NzcwNjYwNDAwMzkwNkwxMzAuNDQyODQwNTc2MTcxODgsMTQyLjE1ODY3NjE0NzQ2MDk0TDEzMC40NDI4NDA1NzYxNzE4OCwxMjcuNjU2MjExODUzMDI3MzRMMTQzLjYyNjkyMjYwNzQyMTg4LDEyNy42NTYyMTE4NTMwMjczNFoiIGNsYXNzPSJzdDAiIHN0eWxlPSJmaWxsOiByZ2IoMjU1LCAxMzAsIDU0KTsgZmlsbC1vcGFjaXR5OiAxOyIvPjwvZz48L2c+PGcgaWQ9InNoYXBlLWVmOTRmYmIwLWRiYWItODBlZC04MDA2LTg5NDIyMzYzZWY0MyI+PGcgY2xhc3M9ImZpbGxzIiBpZD0iZmlsbHMtZWY5NGZiYjAtZGJhYi04MGVkLTgwMDYtODk0MjIzNjNlZjQzIj48cGF0aCBkPSJNMTkxLjk2ODIzMTIwMTE3MTg4LDEyNy42NTYyMTE4NTMwMjczNEwxOTEuOTY4MjMxMjAxMTcxODgsMTQyLjE1ODY3NjE0NzQ2MDk0TDE5My4yODY4MzQ3MTY3OTY4OCwxNDMuNDc3MDY2MDQwMDM5MDZMMjA2LjkxMDM2OTg3MzA0Njg4LDE0My40NzcwNjYwNDAwMzkwNkwyMDYuOTEwMzY5ODczMDQ2ODgsMTU1Ljc4MjE4MDc4NjEzMjhMMTkxLjk2ODIzMTIwMTE3MTg4LDE1NS43ODIxODA3ODYxMzI4TDE5MS45NjgyMzEyMDExNzE4OCwxNzAuNzI0MDc1MzE3MzgyOEwxNzguNzg0MzkzMzEwNTQ2ODgsMTcwLjcyNDA3NTMxNzM4MjhMMTc4Ljc4NDM5MzMxMDU0Njg4LDE1NS43ODIxODA3ODYxMzI4TDE2NC43MjE0MDUwMjkyOTY4OCwxNTUuNzgyMTgwNzg2MTMyOEwxNjQuNzIxNDA1MDI5Mjk2ODgsMTQzLjQ3NzA2NjA0MDAzOTA2TDE3OC43ODQzOTMzMTA1NDY4OCwxNDMuNDc3MDY2MDQwMDM5MDZMMTc4Ljc4NDM5MzMxMDU0Njg4LDEyNy42NTYyMTE4NTMwMjczNEwxOTEuOTY4MjMxMjAxMTcxODgsMTI3LjY1NjIxMTg1MzAyNzM0WiIgY2xhc3M9InN0MCIgc3R5bGU9ImZpbGw6IHJnYigyNTUsIDEzMCwgNTQpOyBmaWxsLW9wYWNpdHk6IDE7Ii8+PC9nPjwvZz48ZyBpZD0ic2hhcGUtZWY5NGZiYjAtZGJhYi04MGVkLTgwMDYtODk0MjIzNjNlZjQ0Ij48ZyBjbGFzcz0iZmlsbHMiIGlkPSJmaWxscy1lZjk0ZmJiMC1kYmFiLTgwZWQtODAwNi04OTQyMjM2M2VmNDQiPjxwYXRoIGQ9Ik0xNTMuMjA3NDg5MDEzNjcxODgsMzguMDkyNjU1MTgxODg0NzY2QzE1NC45NjU1NDU2NTQyOTY4OCw0MC43Mjk0NjU0ODQ2MTkxNCwxNDUuMDMzNDE2NzQ4MDQ2ODgsNTIuMDY3NzA3MDYxNzY3NTgsMTQzLjQ1MTE0MTM1NzQyMTg4LDU0Ljk2ODE3Mzk4MDcxMjg5QzEzOC44ODA4Mjg4NTc0MjE4OCw2My41ODE3OTA5MjQwNzIyNjYsMTQxLjk1NzAwMDczMjQyMTg4LDY4LjUwMzgyMjMyNjY2MDE2LDE0NS4zODQ3MzUxMDc0MjE4OCw3Ni42Nzc5MjUxMDk4NjMyOEMxMzUuNDUyODUwMzQxNzk2ODgsNzUuMTgzNzIzNDQ5NzA3MDMsMTI2LjIyNDA5ODIwNTU2NjQsNzYuNDE0MjUzMjM0ODYzMjgsMTE2LjM3OTg1OTkyNDMxNjQsNzcuNTU2ODMxMzU5ODYzMjhDMTE4LjU3NzM2OTY4OTk0MTQsNTguNjU5NzMyODE4NjAzNTE2LDEyOS4yMTI2MTU5NjY3OTY4OCwzMS4xNDkwNTM1NzM2MDg0LDE1My4yMDc0ODkwMTM2NzE4OCwzOC4wOTI2NTUxODE4ODQ3NjZaIiBjbGFzcz0ic3QwIiBzdHlsZT0iZmlsbDogcmdiKDI1NSwgMTMwLCA1NCk7IGZpbGwtb3BhY2l0eTogMTsiLz48L2c+PC9nPjwvZz48L2c+PC9zdmc+" />
1212
<meta name="viewport" content="width=device-width, initial-scale=1" />
13-
<link href="./bundle.js" rel="modulepreload">
1413

1514
<link href="./bundle.css" rel="stylesheet">
15+
<link rel="modulepreload" href="./bundle.js">
1616
</head>
1717
<body data-sveltekit-preload-data="hover">
1818
<div style="display: contents">
1919
<script>
2020
{
21-
<<<<<<< HEAD
22-
__sveltekit_1610ad9 = {
23-
=======
24-
__sveltekit_1c5cn2q = {
25-
>>>>>>> e21d52141 (feat: Trigger pre-encoding only when server slots are idle)
21+
__sveltekit_nl4lme = {
2622
base: new URL('.', location).pathname.slice(0, -1)
2723
};
2824

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,17 +293,17 @@
293293
fields: [
294294
{
295295
key: SETTINGS_KEYS.PRE_ENCODE_CONVERSATION,
296-
label: 'Pre-encode conversation after response',
296+
label: 'Pre-fill KV cache after response',
297297
type: SettingsFieldType.CHECKBOX
298298
},
299299
{
300300
key: SETTINGS_KEYS.DISABLE_REASONING_PARSING,
301-
label: 'Disable reasoning content parsing',
301+
label: 'Disable server-side thinking extraction',
302302
type: SettingsFieldType.CHECKBOX
303303
},
304304
{
305305
key: SETTINGS_KEYS.EXCLUDE_REASONING_FROM_CONTEXT,
306-
label: 'Exclude reasoning from context',
306+
label: 'Strip thinking from message history',
307307
type: SettingsFieldType.CHECKBOX
308308
},
309309
{

tools/server/webui/src/lib/constants/settings-config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
107107
custom: 'Custom JSON parameters to send to the API. Must be valid JSON format.',
108108
showThoughtInProgress: 'Expand thought process by default when generating messages.',
109109
disableReasoningParsing:
110-
'Send reasoning_format=none to prevent server-side extraction of reasoning tokens into separate field',
110+
'Send reasoning_format=none so the server returns thinking tokens inline instead of extracting them into a separate field.',
111111
excludeReasoningFromContext:
112-
'Strip reasoning content from previous messages before sending to the model. When unchecked, reasoning is sent back via the reasoning_content field so the model can see its own chain-of-thought across turns.',
112+
'Strip thinking from previous messages before sending. When off, thinking is sent back via the reasoning_content field so the model sees its own chain-of-thought across turns.',
113113
showRawOutputSwitch:
114114
'Show toggle button to display messages as plain text instead of Markdown-formatted content',
115115
keepStatsVisible: 'Keep processing statistics visible after generation finishes.',
@@ -145,7 +145,7 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
145145
pyInterpreterEnabled:
146146
'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.',
147147
preEncodeConversation:
148-
'After each response, automatically re-submit the conversation (without chain-of-thought) to pre-fill the server KV cache. This makes the next turn faster since the prompt is already encoded while you read the response.',
148+
'After each response, re-submit the conversation to pre-fill the server KV cache. Makes the next turn faster since the prompt is already encoded while you read the response.',
149149
enableContinueGeneration:
150150
'Enable "Continue" button for assistant messages. Currently works only with non-reasoning models.'
151151
};

tools/server/webui/src/lib/services/chat.service.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,23 @@ export class ChatService {
305305

306306
/**
307307
* Sends a fire-and-forget request to pre-encode the conversation in the server's KV cache.
308-
* After a response completes, this re-submits the full conversation (with reasoning/CoT stripped)
308+
* After a response completes, this re-submits the full conversation
309309
* using n_predict=0 and stream=false so the server processes the prompt without generating tokens.
310310
* This warms the cache for the next turn, making it faster.
311311
*
312+
* When excludeReasoningFromContext is true, reasoning content is stripped from the messages
313+
* to match what sendMessage would send on the next turn (avoiding cache misses).
314+
* When false, reasoning_content is preserved so the cached prompt matches the next request.
315+
*
312316
* @param messages - The full conversation including the latest assistant response
313317
* @param model - Optional model name (required in ROUTER mode)
318+
* @param excludeReasoning - Whether to strip reasoning content (should match excludeReasoningFromContext setting)
314319
* @param signal - Optional AbortSignal to cancel the pre-encode request
315320
*/
316321
static async preEncode(
317322
messages: ApiChatMessageData[] | (DatabaseMessage & { extra?: DatabaseMessageExtra[] })[],
318323
model?: string | null,
324+
excludeReasoning?: boolean,
319325
signal?: AbortSignal
320326
): Promise<void> {
321327
const normalizedMessages: ApiChatMessageData[] = messages
@@ -325,23 +331,34 @@ export class ChatService {
325331
msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] }
326332
);
327333
}
334+
328335
return msg as ApiChatMessageData;
329336
})
330337
.filter((msg) => {
331338
if (msg.role === MessageRole.SYSTEM) {
332339
const content = typeof msg.content === 'string' ? msg.content : '';
340+
333341
return content.trim().length > 0;
334342
}
343+
335344
return true;
336345
});
337346

338347
const requestBody: Record<string, unknown> = {
339-
messages: normalizedMessages.map((msg: ApiChatMessageData) => ({
340-
role: msg.role,
341-
content: ChatService.stripReasoningContent(msg.content),
342-
tool_calls: msg.tool_calls,
343-
tool_call_id: msg.tool_call_id
344-
})),
348+
messages: normalizedMessages.map((msg: ApiChatMessageData) => {
349+
const mapped: Record<string, unknown> = {
350+
role: msg.role,
351+
content: excludeReasoning ? ChatService.stripReasoningContent(msg.content) : msg.content,
352+
tool_calls: msg.tool_calls,
353+
tool_call_id: msg.tool_call_id
354+
};
355+
356+
if (!excludeReasoning && msg.reasoning_content) {
357+
mapped.reasoning_content = msg.reasoning_content;
358+
}
359+
360+
return mapped;
361+
}),
345362
stream: false,
346363
n_predict: 0
347364
};

tools/server/webui/src/lib/stores/chat.svelte.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,13 @@ class ChatStore {
730730
if (isRouterMode()) modelsStore.fetchRouterModels().catch(console.error);
731731
// Pre-encode conversation in KV cache for faster next turn
732732
if (config().preEncodeConversation) {
733-
this.triggerPreEncode(allMessages, assistantMessage, streamedContent, effectiveModel);
733+
this.triggerPreEncode(
734+
allMessages,
735+
assistantMessage,
736+
streamedContent,
737+
effectiveModel,
738+
!!config().excludeReasoningFromContext
739+
);
734740
}
735741
},
736742
onError: (error: Error) => {
@@ -1638,7 +1644,8 @@ class ChatStore {
16381644
allMessages: DatabaseMessage[],
16391645
assistantMessage: DatabaseMessage,
16401646
assistantContent: string,
1641-
model?: string | null
1647+
model?: string | null,
1648+
excludeReasoning?: boolean
16421649
): Promise<void> {
16431650
this.cancelPreEncode();
16441651
this.preEncodeAbortController = new AbortController();
@@ -1654,7 +1661,7 @@ class ChatStore {
16541661
{ ...assistantMessage, content: assistantContent }
16551662
];
16561663

1657-
await ChatService.preEncode(messagesWithAssistant, model, signal);
1664+
await ChatService.preEncode(messagesWithAssistant, model, excludeReasoning, signal);
16581665
} catch (err) {
16591666
if (!isAbortError(err)) {
16601667
console.warn('[ChatStore] Pre-encode failed:', err);

0 commit comments

Comments
 (0)