enh: <think> tag support

This commit is contained in:
Timothy Jaeryang Baek 2025-01-22 00:13:24 -08:00
parent 8d3c73aed5
commit c9dc7299c5
7 changed files with 116 additions and 54 deletions

View File

@ -1072,6 +1072,12 @@ async def process_chat_response(
},
)
# We might want to disable this by default
detect_reasoning = True
reasoning_start_time = None
reasoning_content = ""
async for line in response.body_iterator:
line = line.decode("utf-8") if isinstance(line, bytes) else line
data = line
@ -1098,7 +1104,6 @@ async def process_chat_response(
"selectedModelId": data["selected_model_id"],
},
)
else:
value = (
data.get("choices", [])[0]
@ -1109,6 +1114,39 @@ async def process_chat_response(
if value:
content = f"{content}{value}"
if detect_reasoning:
if "<think>\n" in content:
reasoning_start_time = time.time()
reasoning_content = ""
content = content.replace("<think>\n", "")
if reasoning_start_time is not None:
reasoning_content += value
if "</think>\n" in reasoning_content:
reasoning_end_time = time.time()
reasoning_duration = int(
reasoning_end_time
- reasoning_start_time
)
reasoning_content = (
reasoning_content.strip("<think>\n")
.strip("</think>\n")
.strip()
)
if reasoning_content:
# Format reasoning with <details> tag
content = f"<details>\n<summary>Thought for {reasoning_duration} seconds</summary>\n{reasoning_content}\n</details>\n"
else:
content = ""
reasoning_start_time = None
else:
# Show ongoing thought process
content = f"<details>\n<summary>Thinking… <loading/></summary>\n{reasoning_content}\n</details>\n"
if ENABLE_REALTIME_CHAT_SAVE:
# Save message in the database
Chats.upsert_message_to_chat_by_id_and_message_id(
@ -1129,10 +1167,8 @@ async def process_chat_response(
"data": data,
}
)
except Exception as e:
done = "data: [DONE]" in line
if done:
pass
else:

View File

@ -127,6 +127,50 @@ select {
-webkit-appearance: none;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.shimmer {
background: linear-gradient(90deg, #9a9b9e 25%, #2a2929 50%, #9a9b9e 75%);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shimmer 4s linear infinite;
color: #818286; /* Fallback color */
}
:global(.dark) .shimmer {
background: linear-gradient(90deg, #818286 25%, #eae5e5 50%, #818286 75%);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shimmer 4s linear infinite;
color: #a1a3a7; /* Darker fallback color for dark mode */
}
@keyframes smoothFadeIn {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.status-description {
animation: smoothFadeIn 0.2s forwards;
}
.katex-mathml {
display: none;
}

View File

@ -195,7 +195,11 @@
</ul>
{/if}
{:else if token.type === 'details'}
<Collapsible title={token.summary} className="w-fit space-y-1">
<Collapsible
title={token.summary}
isLoading={token?.isLoading ?? false}
className="w-fit space-y-1"
>
<div class=" mb-1.5" slot="content">
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
</div>

View File

@ -1,3 +1,3 @@
<div class=" self-center font-semibold mb-0.5 line-clamp-1 flex gap-1 items-center">
<div class=" self-center font-semibold line-clamp-1 flex gap-1 items-center">
<slot />
</div>

View File

@ -1234,48 +1234,4 @@
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.shimmer {
background: linear-gradient(90deg, #9a9b9e 25%, #2a2929 50%, #9a9b9e 75%);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shimmer 4s linear infinite;
color: #818286; /* Fallback color */
}
:global(.dark) .shimmer {
background: linear-gradient(90deg, #818286 25%, #eae5e5 50%, #818286 75%);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shimmer 4s linear infinite;
color: #a1a3a7; /* Darker fallback color for dark mode */
}
@keyframes smoothFadeIn {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.status-description {
animation: smoothFadeIn 0.2s forwards;
}
</style>

View File

@ -9,12 +9,14 @@
import ChevronUp from '../icons/ChevronUp.svelte';
import ChevronDown from '../icons/ChevronDown.svelte';
import Spinner from './Spinner.svelte';
export let open = false;
export let className = '';
export let buttonClassName =
'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition';
export let title = null;
export let isLoading = false;
export let grow = false;
@ -34,12 +36,23 @@
}
}}
>
<div class=" w-full font-medium flex items-center justify-between gap-2">
<div
class=" w-full font-medium flex items-center justify-between gap-2 {isLoading === true
? 'shimmer'
: ''}
"
>
{#if isLoading}
<div>
<Spinner className="size-4" />
</div>
{/if}
<div class="">
{title}
</div>
<div>
<div class="flex self-center translate-y-[1px]">
{#if open}
<ChevronUp strokeWidth="3.5" className="size-3.5" />
{:else}

View File

@ -18,18 +18,26 @@ function findMatchingClosingTag(src, openTag, closeTag) {
function detailsTokenizer(src) {
const detailsRegex = /^<details>\n/;
const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
const loadingRegex = /<loading\s*\/>/; // Detect <loading/>
if (detailsRegex.test(src)) {
const endIndex = findMatchingClosingTag(src, '<details>', '</details>');
if (endIndex === -1) return;
const fullMatch = src.slice(0, endIndex);
let content = fullMatch.slice(10, -10).trim(); // Remove <details> and </details>
let summary = '';
let isLoading = false;
const summaryMatch = summaryRegex.exec(content);
if (summaryMatch) {
summary = summaryMatch[1].trim();
// Detect and remove <loading/>
if (loadingRegex.test(summary)) {
isLoading = true;
summary = summary.replace(loadingRegex, '').trim(); // Remove <loading/> from summary
}
content = content.slice(summaryMatch[0].length).trim();
}
@ -37,7 +45,8 @@ function detailsTokenizer(src) {
type: 'details',
raw: fullMatch,
summary: summary,
text: content
text: content,
isLoading: isLoading // Include loading property to indicate if <loading/> was present
};
}
}