mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
fix(frontend): resolve issue preventing cost from displaying (V1) (#11798)
This commit is contained in:
parent
2e295073ae
commit
3219834e35
@ -12,7 +12,8 @@ export function ContextWindowSection({
|
||||
}: ContextWindowSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const usagePercentage = (perTurnToken / contextWindow) * 100;
|
||||
const usagePercentage =
|
||||
contextWindow > 0 ? (perTurnToken / contextWindow) * 100 : 0;
|
||||
const progressWidth = Math.min(100, usagePercentage);
|
||||
|
||||
return (
|
||||
|
||||
@ -22,10 +22,12 @@ import {
|
||||
isConversationStateUpdateEvent,
|
||||
isFullStateConversationStateUpdateEvent,
|
||||
isAgentStatusConversationStateUpdateEvent,
|
||||
isStatsConversationStateUpdateEvent,
|
||||
isExecuteBashActionEvent,
|
||||
isExecuteBashObservationEvent,
|
||||
isConversationErrorEvent,
|
||||
} from "#/types/v1/type-guards";
|
||||
import { ConversationStateUpdateEventStats } from "#/types/v1/core/events/conversation-state-event";
|
||||
import { handleActionEventCacheInvalidation } from "#/utils/cache-utils";
|
||||
import { buildWebSocketUrl } from "#/utils/websocket-url";
|
||||
import type {
|
||||
@ -36,6 +38,7 @@ import EventService from "#/api/event-service/event-service.api";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import { isBudgetOrCreditError } from "#/utils/error-handler";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
import useMetricsStore from "#/stores/metrics-store";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export type V1_WebSocketConnectionState =
|
||||
@ -105,6 +108,37 @@ export function ConversationWebSocketProvider({
|
||||
const receivedEventCountRefMain = useRef(0);
|
||||
const receivedEventCountRefPlanning = useRef(0);
|
||||
|
||||
// Helper function to update metrics from stats event
|
||||
const updateMetricsFromStats = useCallback(
|
||||
(event: ConversationStateUpdateEventStats) => {
|
||||
if (event.value.usage_to_metrics?.agent) {
|
||||
const agentMetrics = event.value.usage_to_metrics.agent;
|
||||
const metrics = {
|
||||
cost: agentMetrics.accumulated_cost,
|
||||
max_budget_per_task: agentMetrics.max_budget_per_task ?? null,
|
||||
usage: agentMetrics.accumulated_token_usage
|
||||
? {
|
||||
prompt_tokens:
|
||||
agentMetrics.accumulated_token_usage.prompt_tokens,
|
||||
completion_tokens:
|
||||
agentMetrics.accumulated_token_usage.completion_tokens,
|
||||
cache_read_tokens:
|
||||
agentMetrics.accumulated_token_usage.cache_read_tokens,
|
||||
cache_write_tokens:
|
||||
agentMetrics.accumulated_token_usage.cache_write_tokens,
|
||||
context_window:
|
||||
agentMetrics.accumulated_token_usage.context_window,
|
||||
per_turn_token:
|
||||
agentMetrics.accumulated_token_usage.per_turn_token,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
useMetricsStore.getState().setMetrics(metrics);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Build WebSocket URL from props
|
||||
// Only build URL if we have both conversationId and conversationUrl
|
||||
// This prevents connection attempts during task polling phase
|
||||
@ -287,6 +321,9 @@ export function ConversationWebSocketProvider({
|
||||
if (isAgentStatusConversationStateUpdateEvent(event)) {
|
||||
setExecutionStatus(event.value);
|
||||
}
|
||||
if (isStatsConversationStateUpdateEvent(event)) {
|
||||
updateMetricsFromStats(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ExecuteBashAction events - add command as input to terminal
|
||||
@ -320,6 +357,7 @@ export function ConversationWebSocketProvider({
|
||||
setExecutionStatus,
|
||||
appendInput,
|
||||
appendOutput,
|
||||
updateMetricsFromStats,
|
||||
],
|
||||
);
|
||||
|
||||
@ -381,6 +419,9 @@ export function ConversationWebSocketProvider({
|
||||
if (isAgentStatusConversationStateUpdateEvent(event)) {
|
||||
setExecutionStatus(event.value);
|
||||
}
|
||||
if (isStatsConversationStateUpdateEvent(event)) {
|
||||
updateMetricsFromStats(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ExecuteBashAction events - add command as input to terminal
|
||||
@ -414,6 +455,7 @@ export function ConversationWebSocketProvider({
|
||||
setExecutionStatus,
|
||||
appendInput,
|
||||
appendOutput,
|
||||
updateMetricsFromStats,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -1,11 +1,63 @@
|
||||
import { BaseEvent } from "../base/event";
|
||||
import { V1ExecutionStatus } from "../base/common";
|
||||
|
||||
/**
|
||||
* Token usage metrics for LLM calls
|
||||
*/
|
||||
export interface TokenUsage {
|
||||
model: string;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_read_tokens: number;
|
||||
cache_write_tokens: number;
|
||||
reasoning_tokens: number;
|
||||
context_window: number;
|
||||
per_turn_token: number;
|
||||
response_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LLM metrics for a specific component (agent or condenser)
|
||||
*/
|
||||
export interface LLMMetrics {
|
||||
model_name: string;
|
||||
accumulated_cost: number;
|
||||
max_budget_per_task: number | null;
|
||||
accumulated_token_usage: TokenUsage;
|
||||
costs: Array<{
|
||||
model: string;
|
||||
cost: number;
|
||||
timestamp: number;
|
||||
}>;
|
||||
response_latencies: Array<{
|
||||
model: string;
|
||||
latency: number;
|
||||
response_id: string;
|
||||
}>;
|
||||
token_usages: TokenUsage[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage metrics mapping for different components
|
||||
*/
|
||||
export interface UsageToMetrics {
|
||||
agent: LLMMetrics;
|
||||
condenser: LLMMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats containing usage metrics
|
||||
*/
|
||||
export interface ConversationStats {
|
||||
usage_to_metrics: UsageToMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation state value types
|
||||
*/
|
||||
export interface ConversationState {
|
||||
execution_status: V1ExecutionStatus;
|
||||
stats?: ConversationStats;
|
||||
// Add other conversation state fields here as needed
|
||||
}
|
||||
|
||||
@ -19,12 +71,12 @@ interface ConversationStateUpdateEventBase extends BaseEvent {
|
||||
* Unique key for this state update event.
|
||||
* Can be "full_state" for full state snapshots or field names for partial updates.
|
||||
*/
|
||||
key: "full_state" | "execution_status"; // Extend with other keys as needed
|
||||
key: "full_state" | "execution_status" | "stats"; // Extend with other keys as needed
|
||||
|
||||
/**
|
||||
* Conversation state updates
|
||||
*/
|
||||
value: ConversationState | V1ExecutionStatus;
|
||||
value: ConversationState | V1ExecutionStatus | ConversationStats;
|
||||
}
|
||||
|
||||
// Narrowed interfaces for full state update event
|
||||
@ -41,10 +93,18 @@ export interface ConversationStateUpdateEventAgentStatus
|
||||
value: V1ExecutionStatus;
|
||||
}
|
||||
|
||||
// Narrowed interface for stats update event
|
||||
export interface ConversationStateUpdateEventStats
|
||||
extends ConversationStateUpdateEventBase {
|
||||
key: "stats";
|
||||
value: ConversationStats;
|
||||
}
|
||||
|
||||
// Conversation state update event - contains conversation state updates
|
||||
export type ConversationStateUpdateEvent =
|
||||
| ConversationStateUpdateEventFullState
|
||||
| ConversationStateUpdateEventAgentStatus;
|
||||
| ConversationStateUpdateEventAgentStatus
|
||||
| ConversationStateUpdateEventStats;
|
||||
|
||||
// Conversation error event - contains error information
|
||||
export interface ConversationErrorEvent extends BaseEvent {
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
ConversationStateUpdateEvent,
|
||||
ConversationStateUpdateEventAgentStatus,
|
||||
ConversationStateUpdateEventFullState,
|
||||
ConversationStateUpdateEventStats,
|
||||
ConversationErrorEvent,
|
||||
} from "./core/events/conversation-state-event";
|
||||
import { SystemPromptEvent } from "./core/events/system-event";
|
||||
@ -144,6 +145,10 @@ export const isAgentStatusConversationStateUpdateEvent = (
|
||||
): event is ConversationStateUpdateEventAgentStatus =>
|
||||
event.key === "execution_status";
|
||||
|
||||
export const isStatsConversationStateUpdateEvent = (
|
||||
event: ConversationStateUpdateEvent,
|
||||
): event is ConversationStateUpdateEventStats => event.key === "stats";
|
||||
|
||||
/**
|
||||
* Type guard function to check if an event is a conversation error event
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user