Skip to content

Commit 4f45062

Browse files
committed
feat: Improve thinking content parsing
1 parent fcfb13f commit 4f45062

File tree

1 file changed

+79
-31
lines changed

1 file changed

+79
-31
lines changed

tools/server/webui/src/lib/utils/thinking.ts

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
/**
2-
* Parses thinking content from a message that may contain <think> tags
2+
* Parses thinking content from a message that may contain <think> tags or [THINK] tags
33
* Returns an object with thinking content and cleaned message content
4-
* Handles both complete <think>...</think> blocks and incomplete <think> blocks (streaming)
4+
* Handles both complete blocks and incomplete blocks (streaming)
5+
* Supports formats: <think>...</think> and [THINK]...[/THINK]
56
* @param content - The message content to parse
67
* @returns An object containing the extracted thinking content and the cleaned message content
78
*/
89
export function parseThinkingContent(content: string): {
910
thinking: string | null;
1011
cleanContent: string;
1112
} {
12-
const incompleteMatch = content.includes('<think>') && !content.includes('</think>');
13+
const incompleteThinkMatch = content.includes('<think>') && !content.includes('</think>');
14+
const incompleteThinkBracketMatch = content.includes('[THINK]') && !content.includes('[/THINK]');
1315

14-
if (incompleteMatch) {
15-
// Remove the entire <think>... part from clean content
16+
if (incompleteThinkMatch) {
1617
const cleanContent = content.split('</think>')?.[1]?.trim();
17-
// Extract everything after <think> as thinking content
1818
const thinkingContent = content.split('<think>')?.[1]?.trim();
1919

2020
return {
@@ -23,12 +23,35 @@ export function parseThinkingContent(content: string): {
2323
};
2424
}
2525

26-
const completeMatch = content.match(/<think>([\s\S]*?)<\/think>/);
26+
if (incompleteThinkBracketMatch) {
27+
const cleanContent = content.split('[/THINK]')?.[1]?.trim();
28+
const thinkingContent = content.split('[THINK]')?.[1]?.trim();
2729

28-
if (completeMatch) {
29-
const thinkingContent = completeMatch[1]?.trim() ?? '';
30-
const cleanContent = `${content.slice(0, completeMatch.index ?? 0)}${content.slice(
31-
(completeMatch.index ?? 0) + completeMatch[0].length
30+
return {
31+
cleanContent,
32+
thinking: thinkingContent
33+
};
34+
}
35+
36+
const completeThinkMatch = content.match(/<think>([\s\S]*?)<\/think>/);
37+
const completeThinkBracketMatch = content.match(/\[THINK\]([\s\S]*?)\[\/THINK\]/);
38+
39+
if (completeThinkMatch) {
40+
const thinkingContent = completeThinkMatch[1]?.trim() ?? '';
41+
const cleanContent = `${content.slice(0, completeThinkMatch.index ?? 0)}${content.slice(
42+
(completeThinkMatch.index ?? 0) + completeThinkMatch[0].length
43+
)}`.trim();
44+
45+
return {
46+
thinking: thinkingContent,
47+
cleanContent
48+
};
49+
}
50+
51+
if (completeThinkBracketMatch) {
52+
const thinkingContent = completeThinkBracketMatch[1]?.trim() ?? '';
53+
const cleanContent = `${content.slice(0, completeThinkBracketMatch.index ?? 0)}${content.slice(
54+
(completeThinkBracketMatch.index ?? 0) + completeThinkBracketMatch[0].length
3255
)}`.trim();
3356

3457
return {
@@ -44,50 +67,75 @@ export function parseThinkingContent(content: string): {
4467
}
4568

4669
/**
47-
* Checks if content contains an opening <think> tag (for streaming)
70+
* Checks if content contains an opening thinking tag (for streaming)
71+
* Supports both <think> and [THINK] formats
4872
* @param content - The message content to check
49-
* @returns True if the content contains an opening <think> tag
73+
* @returns True if the content contains an opening thinking tag
5074
*/
5175
export function hasThinkingStart(content: string): boolean {
52-
return content.includes('<think>') || content.includes('<|channel|>analysis');
76+
return (
77+
content.includes('<think>') ||
78+
content.includes('[THINK]') ||
79+
content.includes('<|channel|>analysis')
80+
);
5381
}
5482

5583
/**
56-
* Checks if content contains a closing </think> tag (for streaming)
84+
* Checks if content contains a closing thinking tag (for streaming)
85+
* Supports both </think> and [/THINK] formats
5786
* @param content - The message content to check
58-
* @returns True if the content contains a closing </think> tag
87+
* @returns True if the content contains a closing thinking tag
5988
*/
6089
export function hasThinkingEnd(content: string): boolean {
61-
return content.includes('</think>');
90+
return content.includes('</think>') || content.includes('[/THINK]');
6291
}
6392

6493
/**
6594
* Extracts partial thinking content during streaming
66-
* Used when we have <think> but not yet </think>
95+
* Supports both <think> and [THINK] formats
96+
* Used when we have opening tag but not yet closing tag
6797
* @param content - The message content to extract partial thinking from
6898
* @returns An object containing the extracted partial thinking content and the remaining content
6999
*/
70100
export function extractPartialThinking(content: string): {
71101
thinking: string | null;
72102
remainingContent: string;
73103
} {
74-
const startIndex = content.indexOf('<think>');
75-
if (startIndex === -1) {
76-
return { thinking: null, remainingContent: content };
77-
}
104+
const thinkStartIndex = content.indexOf('<think>');
105+
const thinkEndIndex = content.indexOf('</think>');
78106

79-
const endIndex = content.indexOf('</think>');
80-
if (endIndex === -1) {
81-
// Still streaming thinking content
82-
const thinkingStart = startIndex + '<think>'.length;
83-
return {
84-
thinking: content.substring(thinkingStart),
85-
remainingContent: content.substring(0, startIndex)
86-
};
107+
const bracketStartIndex = content.indexOf('[THINK]');
108+
const bracketEndIndex = content.indexOf('[/THINK]');
109+
110+
const useThinkFormat =
111+
thinkStartIndex !== -1 && (bracketStartIndex === -1 || thinkStartIndex < bracketStartIndex);
112+
const useBracketFormat =
113+
bracketStartIndex !== -1 && (thinkStartIndex === -1 || bracketStartIndex < thinkStartIndex);
114+
115+
if (useThinkFormat) {
116+
if (thinkEndIndex === -1) {
117+
const thinkingStart = thinkStartIndex + '<think>'.length;
118+
119+
return {
120+
thinking: content.substring(thinkingStart),
121+
remainingContent: content.substring(0, thinkStartIndex)
122+
};
123+
}
124+
} else if (useBracketFormat) {
125+
if (bracketEndIndex === -1) {
126+
const thinkingStart = bracketStartIndex + '[THINK]'.length;
127+
128+
return {
129+
thinking: content.substring(thinkingStart),
130+
remainingContent: content.substring(0, bracketStartIndex)
131+
};
132+
}
133+
} else {
134+
return { thinking: null, remainingContent: content };
87135
}
88136

89-
// Complete thinking block found
90137
const parsed = parseThinkingContent(content);
138+
91139
return {
92140
thinking: parsed.thinking,
93141
remainingContent: parsed.cleanContent

0 commit comments

Comments
 (0)