feat: added performance statistics info when using embedded LLM
This commit is contained in:
@@ -110,6 +110,7 @@ export class AgentCore {
|
|||||||
let assistantTextContent = '';
|
let assistantTextContent = '';
|
||||||
const accumulatedToolCalls: ToolCall[] = [];
|
const accumulatedToolCalls: ToolCall[] = [];
|
||||||
let finishReason = 'stop';
|
let finishReason = 'stop';
|
||||||
|
let performanceInfo: StreamChunk['performanceInfo'];
|
||||||
|
|
||||||
for await (const chunk of this.llmProvider.generateStream(conversationHistory, systemPrompt, tools)) {
|
for await (const chunk of this.llmProvider.generateStream(conversationHistory, systemPrompt, tools)) {
|
||||||
if (chunk.type === 'text') {
|
if (chunk.type === 'text') {
|
||||||
@@ -120,6 +121,7 @@ export class AgentCore {
|
|||||||
accumulatedToolCalls.push(chunk.toolCall);
|
accumulatedToolCalls.push(chunk.toolCall);
|
||||||
} else if (chunk.type === 'done') {
|
} else if (chunk.type === 'done') {
|
||||||
finishReason = chunk.finishReason ?? 'stop';
|
finishReason = chunk.finishReason ?? 'stop';
|
||||||
|
performanceInfo = chunk.performanceInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,10 +165,9 @@ export class AgentCore {
|
|||||||
// LLM finished with text response (stop reason)
|
// LLM finished with text response (stop reason)
|
||||||
this.agentState.updateMessage(this.currentAssistantMessageId, assistantTextContent);
|
this.agentState.updateMessage(this.currentAssistantMessageId, assistantTextContent);
|
||||||
continueLoop = false;
|
continueLoop = false;
|
||||||
|
yield { type: 'done', content: '', finishReason, performanceInfo };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield { type: 'done', content: '', finishReason: 'stop' };
|
|
||||||
} finally {
|
} finally {
|
||||||
this.currentUserMessageId = null;
|
this.currentUserMessageId = null;
|
||||||
this.currentAssistantMessageId = null;
|
this.currentAssistantMessageId = null;
|
||||||
|
|||||||
@@ -296,6 +296,14 @@ export class LocalBrowserLLMProvider implements LLMProvider {
|
|||||||
yield { type: 'tool_call', content: '', toolCall };
|
yield { type: 'tool_call', content: '', toolCall };
|
||||||
}
|
}
|
||||||
|
|
||||||
yield { type: 'done', content: '', finishReason };
|
yield {
|
||||||
|
type: 'done',
|
||||||
|
content: '',
|
||||||
|
finishReason,
|
||||||
|
performanceInfo: {
|
||||||
|
prefillTps,
|
||||||
|
generationTps,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,16 @@
|
|||||||
|
|
||||||
import type { ToolCall } from '../core/AgentState';
|
import type { ToolCall } from '../core/AgentState';
|
||||||
|
|
||||||
|
export interface PerformanceInfo {
|
||||||
|
prefillTps?: number;
|
||||||
|
generationTps?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StreamChunk {
|
export interface StreamChunk {
|
||||||
type: 'text' | 'tool_call' | 'tool_result' | 'done';
|
type: 'text' | 'tool_call' | 'tool_result' | 'done';
|
||||||
content: string;
|
content: string;
|
||||||
toolCall?: ToolCall;
|
toolCall?: ToolCall;
|
||||||
toolResult?: { name: string; success: boolean; result: string };
|
toolResult?: { name: string; success: boolean; result: string };
|
||||||
finishReason?: string; // 'stop' | 'tool_calls' — present on 'done' chunks
|
performanceInfo?: PerformanceInfo;
|
||||||
|
finishReason?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,6 +226,12 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-performance-info {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #909090;
|
||||||
|
}
|
||||||
|
|
||||||
/* Abort link styling */
|
/* Abort link styling */
|
||||||
.abort-link {
|
.abort-link {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
@@ -310,4 +316,4 @@
|
|||||||
100% {
|
100% {
|
||||||
background-position: -200% 0%;
|
background-position: -200% 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,6 +434,7 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
|
|||||||
key={message.id}
|
key={message.id}
|
||||||
content={message.content}
|
content={message.content}
|
||||||
isStreaming={message.isStreaming}
|
isStreaming={message.isStreaming}
|
||||||
|
performanceInfo={message.performanceInfo}
|
||||||
onAbort={message.isStreaming ? handleAbort : undefined}
|
onAbort={message.isStreaming ? handleAbort : undefined}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
|
import type { PerformanceInfo } from '../../agent/llm/StreamingTypes';
|
||||||
|
|
||||||
interface AssistantMessageProps {
|
interface AssistantMessageProps {
|
||||||
content: string;
|
content: string;
|
||||||
isStreaming?: boolean;
|
isStreaming?: boolean;
|
||||||
onAbort?: () => void;
|
onAbort?: () => void;
|
||||||
|
performanceInfo?: PerformanceInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memoized code component to prevent SyntaxHighlighter re-renders
|
// Memoized code component to prevent SyntaxHighlighter re-renders
|
||||||
@@ -30,7 +32,19 @@ const CodeComponent = memo(({ inline, className, children, ...props }: any) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const AssistantMessage: React.FC<AssistantMessageProps> = ({ content, isStreaming, onAbort }) => {
|
const formatTps = (value?: number): string | null => {
|
||||||
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toFixed(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AssistantMessage: React.FC<AssistantMessageProps> = ({ content, isStreaming, onAbort, performanceInfo }) => {
|
||||||
|
const prefillTps = formatTps(performanceInfo?.prefillTps);
|
||||||
|
const generationTps = formatTps(performanceInfo?.generationTps);
|
||||||
|
const hasPerformanceInfo = Boolean(prefillTps || generationTps);
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
// Handle special abort link for streaming messages
|
// Handle special abort link for streaming messages
|
||||||
if (isStreaming && onAbort && content.includes('click here to abort')) {
|
if (isStreaming && onAbort && content.includes('click here to abort')) {
|
||||||
@@ -83,6 +97,13 @@ const AssistantMessage: React.FC<AssistantMessageProps> = ({ content, isStreamin
|
|||||||
<div className="message-container message-assistant">
|
<div className="message-container message-assistant">
|
||||||
<div className="message-content">
|
<div className="message-content">
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
|
{hasPerformanceInfo && (
|
||||||
|
<div className="message-performance-info">
|
||||||
|
{prefillTps ? `Prefill: ${prefillTps} t/s` : 'Prefill: -'}
|
||||||
|
{' · '}
|
||||||
|
{generationTps ? `Generation: ${generationTps} t/s` : 'Generation: -'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const useStreamProcessor = (options: StreamProcessorOptions): StreamProce
|
|||||||
let assistantResponse = '';
|
let assistantResponse = '';
|
||||||
let tokenCount = 0;
|
let tokenCount = 0;
|
||||||
let hasTextContent = false;
|
let hasTextContent = false;
|
||||||
|
let performanceInfo: ChatMessage['performanceInfo'];
|
||||||
|
|
||||||
console.log(`------------ ${logPrefix} ------------`);
|
console.log(`------------ ${logPrefix} ------------`);
|
||||||
console.log(input);
|
console.log(input);
|
||||||
@@ -65,7 +66,8 @@ export const useStreamProcessor = (options: StreamProcessorOptions): StreamProce
|
|||||||
...msg,
|
...msg,
|
||||||
content: assistantResponse,
|
content: assistantResponse,
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
tokenCount: undefined
|
tokenCount: undefined,
|
||||||
|
performanceInfo
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('------------ ASSISTANT ------------');
|
console.log('------------ ASSISTANT ------------');
|
||||||
@@ -98,19 +100,22 @@ export const useStreamProcessor = (options: StreamProcessorOptions): StreamProce
|
|||||||
assistantResponse = '';
|
assistantResponse = '';
|
||||||
tokenCount = 0;
|
tokenCount = 0;
|
||||||
hasTextContent = false;
|
hasTextContent = false;
|
||||||
|
performanceInfo = undefined;
|
||||||
|
|
||||||
// Create a fresh streaming placeholder for the next LLM response
|
// Create a fresh streaming placeholder for the next LLM response
|
||||||
const nextMsg = createStreamingMessage();
|
const nextMsg = createStreamingMessage();
|
||||||
currentStreamingId = nextMsg.id;
|
currentStreamingId = nextMsg.id;
|
||||||
onMessageAdd(nextMsg);
|
onMessageAdd(nextMsg);
|
||||||
} else if (chunk.type === 'done') {
|
} else if (chunk.type === 'done') {
|
||||||
|
performanceInfo = chunk.performanceInfo;
|
||||||
// Finalize the streaming message
|
// Finalize the streaming message
|
||||||
if (hasTextContent) {
|
if (hasTextContent) {
|
||||||
onMessageUpdate(currentStreamingId, (msg) => ({
|
onMessageUpdate(currentStreamingId, (msg) => ({
|
||||||
...msg,
|
...msg,
|
||||||
content: assistantResponse,
|
content: assistantResponse,
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
tokenCount: undefined
|
tokenCount: undefined,
|
||||||
|
performanceInfo
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// No text in final response — remove empty placeholder
|
// No text in final response — remove empty placeholder
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Transform, type TransformFnParams } from 'class-transformer';
|
import { Transform, type TransformFnParams } from 'class-transformer';
|
||||||
|
import type { PerformanceInfo } from '../agent/llm/StreamingTypes';
|
||||||
|
|
||||||
export interface TimeSignature {
|
export interface TimeSignature {
|
||||||
numerator: number;
|
numerator: number;
|
||||||
@@ -11,6 +12,7 @@ export interface ChatMessage {
|
|||||||
content: string;
|
content: string;
|
||||||
isStreaming?: boolean;
|
isStreaming?: boolean;
|
||||||
tokenCount?: number;
|
tokenCount?: number;
|
||||||
|
performanceInfo?: PerformanceInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user