Skip to content

Commit 98a8eb4

Browse files
fix: resolve critical bugs in LLM cost tracking implementation
- Fix NameError in MetricsService.get_cost_summary() where observer variable was referenced before assignment; refetch from session directly - Add missing threshold= arg to CostThresholdExceededEvent constructor in SessionMetricsObserver (would raise TypeError at runtime) - Fix publish() -> publish_event() call on event bus in base_observer - Add LLM event fields to GalaxyEvent interface; remove (event as any) casts in handleLLMMetricsUpdate and handleCostAlert handlers - Log failed LLMCallEvent emissions at DEBUG instead of silently swallowing - Use stable composite key in RecentCallsTable rows instead of array index - Fix JSX formatting in RightPanel: </div>} -> </div>\n)} style - Update CostByModelChart docstring to reflect generic key usage
1 parent da713e7 commit 98a8eb4

8 files changed

Lines changed: 39 additions & 18 deletions

File tree

galaxy/session/observers/base_observer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,9 @@ async def _handle_llm_call_event(self, event: LLMCallEvent) -> None:
383383
event_type=EventType.COST_THRESHOLD_EXCEEDED,
384384
session_id=self.metrics["session_id"],
385385
total_cost=llm["total_cost"],
386+
threshold=self._cost_alert_threshold,
386387
)
387-
await get_event_bus().publish(threshold_event)
388+
await get_event_bus().publish_event(threshold_event)
388389

389390
def get_metrics(self) -> Dict[str, Any]:
390391
"""

galaxy/webui/frontend/src/components/layout/RightPanel.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ const RightPanel: React.FC = () => {
120120
)}
121121

122122
{/* Constellation Overview - Top half */}
123-
{!isCostView && <div className="flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(147,51,234,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5">
123+
{!isCostView && (
124+
<div className="flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(147,51,234,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5">
124125
<div className="flex items-center justify-between flex-shrink-0">
125126
<div className="flex items-center gap-3">
126127
<Network className="h-5 w-5 text-purple-400 drop-shadow-[0_0_8px_rgba(147,51,234,0.5)]" aria-hidden />
@@ -155,10 +156,12 @@ const RightPanel: React.FC = () => {
155156
variant="embedded"
156157
/>
157158
</div>
158-
</div>}
159+
</div>
160+
)}
159161

160162
{/* TaskStar List or Task Detail - Bottom half */}
161-
{!isCostView && <div className="flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5">
163+
{!isCostView && (
164+
<div className="flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5">
162165
{activeTask ? (
163166
<TaskDetailPanel
164167
task={activeTask}
@@ -181,7 +184,8 @@ const RightPanel: React.FC = () => {
181184
</div>
182185
</>
183186
)}
184-
</div>}
187+
</div>
188+
)}
185189
</div>
186190
);
187191
};

galaxy/webui/frontend/src/components/metrics/CostByModelChart.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface BarChartProps {
88

99
/**
1010
* Horizontal bar chart rendered with pure Tailwind CSS.
11-
* Used to show cost broken down by model name or agent type.
11+
* Used to show cost broken down by a string key (model name, agent type, etc.).
1212
*/
1313
const CostByModelChart: React.FC<BarChartProps> = ({
1414
data,

galaxy/webui/frontend/src/components/metrics/CostDashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const RecentCallsTable: React.FC<RecentCallsTableProps> = ({ calls }) => {
6666
</thead>
6767
<tbody className="divide-y divide-white/5">
6868
{recent.map((call, i) => (
69-
<tr key={i} className="text-slate-300 hover:bg-white/5 transition">
69+
<tr key={`${call.timestamp}-${call.model}-${i}`} className="text-slate-300 hover:bg-white/5 transition">
7070
<td className="py-1.5 font-mono text-slate-400">{formatTs(call.timestamp)}</td>
7171
<td className="py-1.5 max-w-[80px] truncate" title={call.agent_type}>
7272
{call.agent_type}

galaxy/webui/frontend/src/main.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -500,20 +500,20 @@ const handleLLMMetricsUpdate = (event: GalaxyEvent) => {
500500
const store = useGalaxyStore.getState();
501501
const call: LLMCallRecord = {
502502
agent_type: event.agent_type || 'unknown',
503-
model: (event as any).model || 'unknown',
504-
prompt_tokens: (event as any).prompt_tokens ?? 0,
505-
completion_tokens: (event as any).completion_tokens ?? 0,
506-
cost: (event as any).cost ?? 0,
507-
duration_ms: (event as any).duration_ms ?? 0,
503+
model: event.model || 'unknown',
504+
prompt_tokens: event.prompt_tokens ?? 0,
505+
completion_tokens: event.completion_tokens ?? 0,
506+
cost: event.cost ?? 0,
507+
duration_ms: event.duration_ms ?? 0,
508508
timestamp: safeTimestamp(event),
509509
};
510510
store.appendLLMCall(call);
511511
};
512512

513513
const handleCostAlert = (event: GalaxyEvent) => {
514514
const store = useGalaxyStore.getState();
515-
const totalCost: number = (event as any).total_cost ?? 0;
516-
const threshold: number = (event as any).threshold ?? 0;
515+
const totalCost: number = event.total_cost ?? 0;
516+
const threshold: number = event.threshold ?? 0;
517517
store.pushNotification({
518518
id: `cost-alert-${Date.now()}`,
519519
title: 'Cost threshold exceeded',

galaxy/webui/frontend/src/services/websocket.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ export interface GalaxyEvent {
2828
message?: string;
2929
session_name?: string;
3030
task_name?: string;
31+
// LLM metrics events
32+
model?: string;
33+
prompt_tokens?: number;
34+
completion_tokens?: number;
35+
cost?: number;
36+
duration_ms?: number;
37+
// Cost threshold events
38+
total_cost?: number;
39+
threshold?: number;
3140
}
3241

3342
export type EventCallback = (event: GalaxyEvent) => void;

galaxy/webui/services/metrics_service.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,18 @@ def get_cost_summary(self) -> Optional[Dict[str, Any]]:
6666
6767
:return: Cost summary dict or None.
6868
"""
69-
llm = self._get_llm_metrics()
69+
session = self._app_state.galaxy_session
70+
if session is None:
71+
return None
72+
73+
observer = getattr(session, "_metrics_observer", None)
74+
if observer is None:
75+
return None
76+
77+
llm = observer.metrics.get("llm_metrics")
7078
if llm is None:
7179
return None
7280

73-
session = self._app_state.galaxy_session
7481
session_id: str = observer.metrics.get("session_id", "unknown")
7582

7683
raw_calls: List[Dict[str, Any]] = llm.get("calls", [])

ufo/llm/llm_call.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,5 @@ def _emit_llm_call_event(agent_type, model: str, cost_result, duration_ms: float
140140
loop.create_task(bus.publish_event(event))
141141
except RuntimeError:
142142
asyncio.run(bus.publish_event(event))
143-
except Exception:
144-
pass
143+
except Exception as exc:
144+
logging.getLogger(__name__).debug("LLMCallEvent emit failed: %s", exc)

0 commit comments

Comments
 (0)