Skip to content

Commit bfe7aad

Browse files
committed
feat: show stop reason of messages
1 parent 3bc2fe0 commit bfe7aad

File tree

4 files changed

+76
-28
lines changed

4 files changed

+76
-28
lines changed

app/client/api.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { CompletionUsage } from "@neet-nestor/web-llm";
1+
import {
2+
ChatCompletionFinishReason,
3+
CompletionUsage,
4+
} from "@neet-nestor/web-llm";
25
import { CacheType, ModelType } from "../store";
36
export const ROLES = ["system", "user", "assistant"] as const;
47
export type MessageRole = (typeof ROLES)[number];
@@ -34,7 +37,11 @@ export interface ChatOptions {
3437
config: LLMConfig;
3538

3639
onUpdate?: (message: string, chunk: string) => void;
37-
onFinish: (message: string, usage?: CompletionUsage) => void;
40+
onFinish: (
41+
message: string,
42+
stopReason: ChatCompletionFinishReason,
43+
usage?: CompletionUsage,
44+
) => void;
3845
onError?: (err: Error) => void;
3946
onController?: (controller: AbortController) => void;
4047
}

app/client/webllm.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ChatCompletion,
1212
WebWorkerMLCEngine,
1313
CompletionUsage,
14+
ChatCompletionFinishReason,
1415
} from "@neet-nestor/web-llm";
1516

1617
import { ChatOptions, LLMApi, LLMConfig, RequestMessage } from "./api";
@@ -108,6 +109,7 @@ export class WebLLMApi implements LLMApi {
108109
}
109110

110111
let reply: string | null = "";
112+
let stopReason: ChatCompletionFinishReason = "stop";
111113
let usage: CompletionUsage | undefined = undefined;
112114
try {
113115
const completion = await this.chatCompletion(
@@ -116,6 +118,7 @@ export class WebLLMApi implements LLMApi {
116118
options.onUpdate,
117119
);
118120
reply = completion.content;
121+
stopReason = completion.stopReason;
119122
usage = completion.usage;
120123
} catch (err: any) {
121124
let errorMessage = err.message || err.toString() || "";
@@ -157,6 +160,7 @@ export class WebLLMApi implements LLMApi {
157160
options.onUpdate,
158161
);
159162
reply = completion.content;
163+
stopReason = completion.stopReason;
160164
usage = completion.usage;
161165
} catch (err: any) {
162166
let errorMessage = err.message || err.toString() || "";
@@ -170,7 +174,7 @@ export class WebLLMApi implements LLMApi {
170174
}
171175

172176
if (reply) {
173-
options.onFinish(reply, usage);
177+
options.onFinish(reply, stopReason, usage);
174178
} else {
175179
options.onError?.(new Error("Empty response generated by LLM"));
176180
}
@@ -236,6 +240,7 @@ export class WebLLMApi implements LLMApi {
236240

237241
if (stream) {
238242
let content: string | null = "";
243+
let stopReason: ChatCompletionFinishReason = "stop";
239244
let usage: CompletionUsage | undefined = undefined;
240245
const asyncGenerator = completion as AsyncIterable<ChatCompletionChunk>;
241246
for await (const chunk of asyncGenerator) {
@@ -246,13 +251,18 @@ export class WebLLMApi implements LLMApi {
246251
if (chunk.usage) {
247252
usage = chunk.usage;
248253
}
254+
if (chunk.choices[0]?.finish_reason) {
255+
stopReason = chunk.choices[0].finish_reason;
256+
}
249257
}
250-
return { content, usage };
258+
return { content, stopReason, usage };
251259
}
252260

261+
const chatCompletion = completion as ChatCompletion;
253262
return {
254-
content: (completion as ChatCompletion).choices[0].message.content,
255-
usage: (completion as ChatCompletion).usage,
263+
content: chatCompletion.choices[0].message.content,
264+
stopReason: chatCompletion.choices[0].finish_reason,
265+
usage: chatCompletion.usage,
256266
};
257267
}
258268
}

app/components/chat.tsx

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,31 +1335,43 @@ function _Chat() {
13351335
</div>
13361336

13371337
<div className={styles["chat-message-action-date"]}>
1338-
{message.usage && (
1338+
{(message.stopReason || message.usage) && (
13391339
<div className={styles.tooltip}>
13401340
<Tooltip
13411341
direction="top"
13421342
content={
13431343
<div style={{ fontSize: config.fontSize }}>
1344-
<span>
1345-
{`Prompt Tokens: ${message.usage.prompt_tokens}`}
1346-
</span>
1347-
<br />
1348-
<span>
1349-
{`Completion Tokens: ${message.usage.completion_tokens}`}
1350-
</span>
1351-
<br />
1352-
<span>
1353-
{`Prefill: ${message.usage.extra.prefill_tokens_per_s.toFixed(
1354-
4,
1355-
)} tokens/sec`}
1356-
</span>
1357-
<br />
1358-
<span>
1359-
{`Decoding: ${message.usage.extra.decode_tokens_per_s.toFixed(
1360-
4,
1361-
)} tokens/sec`}
1362-
</span>
1344+
{message.stopReason && (
1345+
<>
1346+
<span>
1347+
{`Stop Reason: ${message.stopReason}`}
1348+
</span>
1349+
<br />
1350+
</>
1351+
)}
1352+
{message.usage && (
1353+
<>
1354+
<span>
1355+
{`Prompt Tokens: ${message.usage.prompt_tokens}`}
1356+
</span>
1357+
<br />
1358+
<span>
1359+
{`Completion Tokens: ${message.usage.completion_tokens}`}
1360+
</span>
1361+
<br />
1362+
<span>
1363+
{`Prefill: ${message.usage.extra.prefill_tokens_per_s.toFixed(
1364+
4,
1365+
)} tokens/sec`}
1366+
</span>
1367+
<br />
1368+
<span>
1369+
{`Decoding: ${message.usage.extra.decode_tokens_per_s.toFixed(
1370+
4,
1371+
)} tokens/sec`}
1372+
</span>
1373+
</>
1374+
)}
13631375
</div>
13641376
}
13651377
>

app/store/chat.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ import { estimateTokenLength } from "../utils/token";
1616
import { nanoid } from "nanoid";
1717
import { createPersistStore } from "../utils/store";
1818
import { WebLLMApi } from "../client/webllm";
19-
import { CompletionUsage } from "@neet-nestor/web-llm";
19+
import {
20+
ChatCompletionFinishReason,
21+
CompletionUsage,
22+
} from "@neet-nestor/web-llm";
2023

2124
export type ChatMessage = RequestMessage & {
2225
date: string;
2326
streaming?: boolean;
2427
isError?: boolean;
2528
id: string;
29+
stopReason: ChatCompletionFinishReason;
2630
model?: ModelType;
2731
usage?: CompletionUsage;
2832
};
@@ -33,6 +37,7 @@ export function createMessage(override: Partial<ChatMessage>): ChatMessage {
3337
date: new Date().toLocaleString(),
3438
role: "user",
3539
content: "",
40+
stopReason: "stop",
3641
...override,
3742
};
3843
}
@@ -349,9 +354,10 @@ export const useChatStore = createPersistStore(
349354
session.messages = session.messages.concat();
350355
});
351356
},
352-
onFinish(message, usage) {
357+
onFinish(message, stopReason, usage) {
353358
botMessage.streaming = false;
354359
botMessage.usage = usage;
360+
botMessage.stopReason = stopReason;
355361
if (message) {
356362
botMessage.content = message;
357363
get().onNewMessage(botMessage, webllm);
@@ -668,5 +674,18 @@ export const useChatStore = createPersistStore(
668674
},
669675
{
670676
name: StoreKey.Chat,
677+
version: 0.1,
678+
migrate(persistedState, version): any {
679+
if (version < 0.1) {
680+
const store = persistedState as typeof DEFAULT_CHAT_STATE;
681+
store.sessions.forEach((s) => {
682+
s.messages.forEach((m) => {
683+
m.stopReason = "stop";
684+
});
685+
});
686+
return store;
687+
}
688+
return persistedState;
689+
},
671690
},
672691
);

0 commit comments

Comments
 (0)