feat(cli): add --verbose flag to chat command for detailed usage (#238) (#250)

* feat(cli): add --verbose flag to chat command

* Address review comments: add types, rename --verbose to --stats, remove emoji and GPU layers stat

- Add proper types for displayVerboseStats function parameters
- Rename --verbose flag to --stats to avoid conflict with global verbose flag
- Add short flag -t for --stats
- Remove emoji from stats output for cleaner appearance
- Hide numGpuLayers stat as it's not currently populated

* Update src/subcommands/chat.ts

Co-authored-by: Yagil Burowski <yagil@elementlabs.ai>

* Remove short stats flag

* Update Prediction Stats title

* Add second new line ln26

* Remove short stats flag again

---------

Co-authored-by: Yagil Burowski <yagil@elementlabs.ai>
This commit is contained in:
Yorkie
2025-06-20 20:10:38 +01:00
committed by GitHub
parent 7102ef920d
commit 159dff76cd

View File

@ -1,9 +1,11 @@
import { Chat, type LLM } from "@lmstudio/sdk";
import { command, option, optional, string } from "cmd-ts";
import { command, flag, option, optional, string } from "cmd-ts";
import * as readline from "readline";
import { createClient, createClientArgs } from "../createClient.js";
import { createLogger, logLevelArgs } from "../logLevel.js";
import { optionalPositional } from "../optionalPositional.js";
import type { LLMPredictionStats } from "@lmstudio/lms-shared-types";
import type { SimpleLogger } from "@lmstudio/lms-common";
async function readStdin(): Promise<string> {
return new Promise(resolve => {
@ -20,6 +22,26 @@ async function readStdin(): Promise<string> {
});
}
function displayVerboseStats(stats: LLMPredictionStats, logger: SimpleLogger) {
logger.info("\n\nPrediction Stats:");
logger.info(` Stop Reason: ${stats.stopReason}`);
if (stats.tokensPerSecond !== undefined) {
logger.info(` Tokens/Second: ${stats.tokensPerSecond.toFixed(2)}`);
}
if (stats.timeToFirstTokenSec !== undefined) {
logger.info(` Time to First Token: ${stats.timeToFirstTokenSec.toFixed(3)}s`);
}
if (stats.promptTokensCount !== undefined) {
logger.info(` Prompt Tokens: ${stats.promptTokensCount}`);
}
if (stats.predictedTokensCount !== undefined) {
logger.info(` Predicted Tokens: ${stats.predictedTokensCount}`);
}
if (stats.totalTokensCount !== undefined) {
logger.info(` Total Tokens: ${stats.totalTokensCount}`);
}
}
export const chat = command({
name: "chat",
description: "Open an interactive chat with the currently loaded model.",
@ -44,6 +66,10 @@ export const chat = command({
short: "s",
description: "Custom system prompt to use for the chat",
}),
stats: flag({
long: "stats",
description: "Display detailed prediction statistics after each response",
}),
},
async handler(args) {
const logger = createLogger(args);
@ -101,6 +127,10 @@ export const chat = command({
const result = await prediction.result();
chat.append("assistant", result.content);
if (args.stats) {
displayVerboseStats(result.stats, logger);
}
if (!lastFragment.endsWith("\n")) {
// Newline before new shell prompt if not already there
process.stdout.write("\n");
@ -149,6 +179,10 @@ export const chat = command({
const result = await prediction.result();
chat.append("assistant", result.content);
if (args.stats) {
displayVerboseStats(result.stats, logger);
}
// Resume readline and write a new prompt
process.stdout.write("\n\n");
rl.resume();