Skip to content

Commit ecce008

Browse files
authored
fix: Detect streaming state in reasoning content blocks (ggml-org#21549)
1 parent d1f82e3 commit ecce008

5 files changed

Lines changed: 133 additions & 92 deletions

File tree

tools/server/public/bundle.js

Lines changed: 85 additions & 85 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<div style="display: contents">
1919
<script>
2020
{
21-
__sveltekit_1y361v9 = {
21+
__sveltekit_1wqaxod = {
2222
base: new URL('.', location).pathname.slice(0, -1)
2323
};
2424

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAgenticContent.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
const showToolCallInProgress = $derived(config().showToolCallInProgress as boolean);
3434
const showThoughtInProgress = $derived(config().showThoughtInProgress as boolean);
3535
36-
const sections = $derived(deriveAgenticSections(message, toolMessages, []));
36+
const sections = $derived(deriveAgenticSections(message, toolMessages, [], isStreaming));
3737
3838
// Parse tool results with images
3939
const sectionsParsed = $derived(

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,19 @@ export type ToolResultLine = {
3838
function deriveSingleTurnSections(
3939
message: DatabaseMessage,
4040
toolMessages: DatabaseMessage[] = [],
41-
streamingToolCalls: ApiChatCompletionToolCall[] = []
41+
streamingToolCalls: ApiChatCompletionToolCall[] = [],
42+
isStreaming: boolean = false
4243
): AgenticSection[] {
4344
const sections: AgenticSection[] = [];
4445

4546
// 1. Reasoning content (from dedicated field)
4647
if (message.reasoningContent) {
48+
const toolCalls = parseToolCalls(message.toolCalls);
49+
const hasContentAfterReasoning =
50+
!!message.content?.trim() || toolCalls.length > 0 || streamingToolCalls.length > 0;
51+
const isPending = isStreaming && !hasContentAfterReasoning;
4752
sections.push({
48-
type: AgenticSectionType.REASONING,
53+
type: isPending ? AgenticSectionType.REASONING_PENDING : AgenticSectionType.REASONING,
4954
content: message.reasoningContent
5055
});
5156
}
@@ -104,12 +109,13 @@ function deriveSingleTurnSections(
104109
export function deriveAgenticSections(
105110
message: DatabaseMessage,
106111
toolMessages: DatabaseMessage[] = [],
107-
streamingToolCalls: ApiChatCompletionToolCall[] = []
112+
streamingToolCalls: ApiChatCompletionToolCall[] = [],
113+
isStreaming: boolean = false
108114
): AgenticSection[] {
109115
const hasAssistantContinuations = toolMessages.some((m) => m.role === MessageRole.ASSISTANT);
110116

111117
if (!hasAssistantContinuations) {
112-
return deriveSingleTurnSections(message, toolMessages, streamingToolCalls);
118+
return deriveSingleTurnSections(message, toolMessages, streamingToolCalls, isStreaming);
113119
}
114120

115121
const sections: AgenticSection[] = [];
@@ -127,7 +133,12 @@ export function deriveAgenticSections(
127133
const isLastTurn = i + 1 + turnToolMsgs.length >= toolMessages.length;
128134

129135
sections.push(
130-
...deriveSingleTurnSections(msg, turnToolMsgs, isLastTurn ? streamingToolCalls : [])
136+
...deriveSingleTurnSections(
137+
msg,
138+
turnToolMsgs,
139+
isLastTurn ? streamingToolCalls : [],
140+
isLastTurn && isStreaming
141+
)
131142
);
132143

133144
i += 1 + turnToolMsgs.length;

tools/server/webui/tests/unit/agentic-sections.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,36 @@ describe('deriveAgenticSections', () => {
162162
expect(sections[4].content).toBe('Here is the analysis.');
163163
});
164164

165+
it('returns REASONING_PENDING when streaming with only reasoning content', () => {
166+
const msg = makeAssistant({
167+
reasoningContent: 'Let me think about this...'
168+
});
169+
const sections = deriveAgenticSections(msg, [], [], true);
170+
expect(sections).toHaveLength(1);
171+
expect(sections[0].type).toBe(AgenticSectionType.REASONING_PENDING);
172+
expect(sections[0].content).toBe('Let me think about this...');
173+
});
174+
175+
it('returns REASONING (not pending) when streaming but text content has appeared', () => {
176+
const msg = makeAssistant({
177+
content: 'The answer is',
178+
reasoningContent: 'Let me think...'
179+
});
180+
const sections = deriveAgenticSections(msg, [], [], true);
181+
expect(sections).toHaveLength(2);
182+
expect(sections[0].type).toBe(AgenticSectionType.REASONING);
183+
expect(sections[1].type).toBe(AgenticSectionType.TEXT);
184+
});
185+
186+
it('returns REASONING (not pending) when not streaming', () => {
187+
const msg = makeAssistant({
188+
reasoningContent: 'Let me think...'
189+
});
190+
const sections = deriveAgenticSections(msg, [], [], false);
191+
expect(sections).toHaveLength(1);
192+
expect(sections[0].type).toBe(AgenticSectionType.REASONING);
193+
});
194+
165195
it('multi-turn: streaming tool calls on last turn', () => {
166196
const assistant1 = makeAssistant({
167197
toolCalls: JSON.stringify([

0 commit comments

Comments
 (0)