feat: enrich assistant request telemetry

This commit is contained in:
ajnart committed 2026-08-02 08:51:48 +02:00
1 parent d562706c8b
commit 04520cf67c
10 files changed
+798 -39

No files matched your search

@@ -19,7 +19,35 @@ describe("getAssistantTelemetry", () => {
contextUtilization: 0.015625,
cost: 0.001,
costType: "reported",
steps: [{ index: 1, durationMs: 900, modelDurationMs: 800, toolDurationMs: 100 }],
providerOutputTokensPerSecond: 37.9,
generationTimeMs: 1451,
cacheDiscount: 0.00003,
fallbackCount: 1,
steps: [
{
index: 1,
durationMs: 900,
modelDurationMs: 800,
toolDurationMs: 100,
providerLatencyMs: 964,
generationTimeMs: 1451,
providerOutputTokensPerSecond: 37.9,
inputTokens: 12_777,
outputTokens: 55,
normalizedInputTokens: 12_799,
normalizedOutputTokens: 62,
cachedInputTokens: 2048,
reasoningTokens: 7,
cacheDiscount: 0.00003,
fallbackCount: 1,
routedProvider: "Alibaba",
serviceTier: "default",
dataRegion: "US",
isByok: false,
streamed: true,
cancelled: false,
},
],
},
},
}),
@@ -29,6 +57,19 @@ describe("getAssistantTelemetry", () => {
durationMs: 1000,
contextLength: 128_000,
costType: "reported",
providerOutputTokensPerSecond: 37.9,
generationTimeMs: 1451,
cacheDiscount: 0.00003,
fallbackCount: 1,
steps: [
expect.objectContaining({
providerLatencyMs: 964,
inputTokens: 12_777,
cachedInputTokens: 2048,
routedProvider: "Alibaba",
serviceTier: "default",
}),
],
});
});
@@ -16,12 +16,32 @@ export type AssistantRequestStep = {
toolDurationMs: number;
timeToFirstOutputMs?: number;
outputTokensPerSecond?: number;
providerLatencyMs?: number;
generationTimeMs?: number;
moderationLatencyMs?: number;
providerOutputTokensPerSecond?: number;
inputTokens?: number;
outputTokens?: number;
normalizedInputTokens?: number;
normalizedOutputTokens?: number;
cachedInputTokens?: number;
reasoningTokens?: number;
cost?: number;
upstreamCost?: number;
cacheDiscount?: number;
generationId?: string;
routedProvider?: string;
finishReason?: string;
nativeFinishReason?: string;
serviceTier?: string;
dataRegion?: string;
routerStrategy?: string;
routerRegion?: string;
fallbackCount?: number;
fallbackLatencyMs?: number;
isByok?: boolean;
streamed?: boolean;
cancelled?: boolean;
};
export type AssistantRequestTelemetry = {
@@ -33,11 +53,16 @@ export type AssistantRequestTelemetry = {
durationMs?: number;
timeToFirstOutputMs?: number;
outputTokensPerSecond?: number;
providerOutputTokensPerSecond?: number;
generationTimeMs?: number;
contextLength?: number;
contextUsed?: number;
contextUtilization?: number;
cost?: number;
upstreamCost?: number;
cacheDiscount?: number;
fallbackCount?: number;
fallbackLatencyMs?: number;
costType?: "reported" | "estimated";
finishReason?: string;
steps: AssistantRequestStep[];
@@ -124,20 +149,64 @@ export const getAssistantTelemetry = (metadata: unknown): AssistantRequestTeleme
...(getFiniteNonNegativeNumber(candidate.outputTokensPerSecond) !== undefined
? { outputTokensPerSecond: getFiniteNonNegativeNumber(candidate.outputTokensPerSecond) }
: {}),
...(getFiniteNonNegativeNumber(candidate.providerLatencyMs) !== undefined
? { providerLatencyMs: getFiniteNonNegativeNumber(candidate.providerLatencyMs) }
: {}),
...(getFiniteNonNegativeNumber(candidate.generationTimeMs) !== undefined
? { generationTimeMs: getFiniteNonNegativeNumber(candidate.generationTimeMs) }
: {}),
...(getFiniteNonNegativeNumber(candidate.moderationLatencyMs) !== undefined
? { moderationLatencyMs: getFiniteNonNegativeNumber(candidate.moderationLatencyMs) }
: {}),
...(getFiniteNonNegativeNumber(candidate.providerOutputTokensPerSecond) !== undefined
? { providerOutputTokensPerSecond: getFiniteNonNegativeNumber(candidate.providerOutputTokensPerSecond) }
: {}),
...(getFiniteNonNegativeNumber(candidate.inputTokens) !== undefined
? { inputTokens: getFiniteNonNegativeNumber(candidate.inputTokens) }
: {}),
...(getFiniteNonNegativeNumber(candidate.outputTokens) !== undefined
? { outputTokens: getFiniteNonNegativeNumber(candidate.outputTokens) }
: {}),
...(getFiniteNonNegativeNumber(candidate.normalizedInputTokens) !== undefined
? { normalizedInputTokens: getFiniteNonNegativeNumber(candidate.normalizedInputTokens) }
: {}),
...(getFiniteNonNegativeNumber(candidate.normalizedOutputTokens) !== undefined
? { normalizedOutputTokens: getFiniteNonNegativeNumber(candidate.normalizedOutputTokens) }
: {}),
...(getFiniteNonNegativeNumber(candidate.cachedInputTokens) !== undefined
? { cachedInputTokens: getFiniteNonNegativeNumber(candidate.cachedInputTokens) }
: {}),
...(getFiniteNonNegativeNumber(candidate.reasoningTokens) !== undefined
? { reasoningTokens: getFiniteNonNegativeNumber(candidate.reasoningTokens) }
: {}),
...(getFiniteNonNegativeNumber(candidate.cost) !== undefined
? { cost: getFiniteNonNegativeNumber(candidate.cost) }
: {}),
...(getFiniteNonNegativeNumber(candidate.upstreamCost) !== undefined
? { upstreamCost: getFiniteNonNegativeNumber(candidate.upstreamCost) }
: {}),
...(getFiniteNonNegativeNumber(candidate.cacheDiscount) !== undefined
? { cacheDiscount: getFiniteNonNegativeNumber(candidate.cacheDiscount) }
: {}),
...(typeof candidate.generationId === "string" ? { generationId: candidate.generationId } : {}),
...(typeof candidate.routedProvider === "string" ? { routedProvider: candidate.routedProvider } : {}),
...(typeof candidate.finishReason === "string" ? { finishReason: candidate.finishReason } : {}),
...(typeof candidate.nativeFinishReason === "string"
? { nativeFinishReason: candidate.nativeFinishReason }
: {}),
...(typeof candidate.serviceTier === "string" ? { serviceTier: candidate.serviceTier } : {}),
...(typeof candidate.dataRegion === "string" ? { dataRegion: candidate.dataRegion } : {}),
...(typeof candidate.routerStrategy === "string" ? { routerStrategy: candidate.routerStrategy } : {}),
...(typeof candidate.routerRegion === "string" ? { routerRegion: candidate.routerRegion } : {}),
...(getFiniteNonNegativeNumber(candidate.fallbackCount) !== undefined
? { fallbackCount: getFiniteNonNegativeNumber(candidate.fallbackCount) }
: {}),
...(getFiniteNonNegativeNumber(candidate.fallbackLatencyMs) !== undefined
? { fallbackLatencyMs: getFiniteNonNegativeNumber(candidate.fallbackLatencyMs) }
: {}),
...(typeof candidate.isByok === "boolean" ? { isByok: candidate.isByok } : {}),
...(typeof candidate.streamed === "boolean" ? { streamed: candidate.streamed } : {}),
...(typeof candidate.cancelled === "boolean" ? { cancelled: candidate.cancelled } : {}),
},
];
})
@@ -158,6 +227,12 @@ export const getAssistantTelemetry = (metadata: unknown): AssistantRequestTeleme
...(getFiniteNonNegativeNumber(value.outputTokensPerSecond) !== undefined
? { outputTokensPerSecond: getFiniteNonNegativeNumber(value.outputTokensPerSecond) }
: {}),
...(getFiniteNonNegativeNumber(value.providerOutputTokensPerSecond) !== undefined
? { providerOutputTokensPerSecond: getFiniteNonNegativeNumber(value.providerOutputTokensPerSecond) }
: {}),
...(getFiniteNonNegativeNumber(value.generationTimeMs) !== undefined
? { generationTimeMs: getFiniteNonNegativeNumber(value.generationTimeMs) }
: {}),
...(getFiniteNonNegativeNumber(value.contextLength) !== undefined
? { contextLength: getFiniteNonNegativeNumber(value.contextLength) }
: {}),
@@ -171,6 +246,15 @@ export const getAssistantTelemetry = (metadata: unknown): AssistantRequestTeleme
...(getFiniteNonNegativeNumber(value.upstreamCost) !== undefined
? { upstreamCost: getFiniteNonNegativeNumber(value.upstreamCost) }
: {}),
...(getFiniteNonNegativeNumber(value.cacheDiscount) !== undefined
? { cacheDiscount: getFiniteNonNegativeNumber(value.cacheDiscount) }
: {}),
...(getFiniteNonNegativeNumber(value.fallbackCount) !== undefined
? { fallbackCount: getFiniteNonNegativeNumber(value.fallbackCount) }
: {}),
...(getFiniteNonNegativeNumber(value.fallbackLatencyMs) !== undefined
? { fallbackLatencyMs: getFiniteNonNegativeNumber(value.fallbackLatencyMs) }
: {}),
...(value.costType === "reported" || value.costType === "estimated" ? { costType: value.costType } : {}),
...(typeof value.finishReason === "string" ? { finishReason: value.finishReason } : {}),
};
@@ -614,8 +614,8 @@ const formatDuration = (milliseconds: number) =>
const formatCost = (cost: number) => {
if (cost === 0) return "$0";
if (cost < 0.0001) return `<$0.0001`;
return `$${cost.toFixed(cost < 0.01 ? 4 : 3)}`;
if (cost < 0.00001) return `<$0.00001`;
return `$${cost.toFixed(cost < 0.001 ? 5 : cost < 0.01 ? 4 : 3)}`;
};
const getContextColor = (percentage: number) => {
@@ -628,9 +628,101 @@ const RequestTelemetry = () => {
const t = useScopedI18n("common.assistant");
const [opened, setOpened] = useState(false);
const metadata = useAuiState((state) => state.message.metadata);
const telemetry = getAssistantTelemetry(metadata);
const usage = getAssistantUsage(metadata);
if (!telemetry) return null;
const messageId = useAuiState((state) => state.message.id);
const threadId = useAuiState((state) => state.threadListItem.remoteId);
const persistedTelemetry = getAssistantTelemetry(metadata);
const persistedUsage = getAssistantUsage(metadata);
const generationQuery = clientApi.assistant.getGenerationTelemetry.useQuery(
{ threadId: threadId ?? "", messageId },
{
enabled: opened && persistedTelemetry?.provider === "openrouter" && Boolean(threadId),
refetchInterval: (query) =>
query.state.data?.complete === false && query.state.dataUpdateCount < 6 ? 1500 : false,
retry: 2,
staleTime: (query) => (query.state.data?.complete ? 5 * 60_000 : 0),
},
);
if (!persistedTelemetry) return null;
const generationById = new Map(
generationQuery.data?.generations.flatMap((generation) =>
generation.generationId ? [[generation.generationId, generation] as const] : [],
) ?? [],
);
const steps = persistedTelemetry.steps.map((step) => ({
...step,
...(step.generationId ? generationById.get(step.generationId) : undefined),
}));
const sumCompleteMetric = (getValue: (step: (typeof steps)[number]) => number | undefined) => {
if (steps.length === 0) return undefined;
const values = steps.map(getValue);
return values.some((value) => value === undefined)
? undefined
: (values as number[]).reduce((sum, value) => sum + value, 0);
};
const providerInputTokens = sumCompleteMetric((step) => step.inputTokens);
const providerOutputTokens = sumCompleteMetric((step) => step.outputTokens);
const providerGenerationTimeMs = sumCompleteMetric((step) => step.generationTimeMs);
const providerOutputTokensPerSecond =
providerOutputTokens !== undefined && providerGenerationTimeMs !== undefined && providerGenerationTimeMs > 0
? providerOutputTokens / (providerGenerationTimeMs / 1000)
: undefined;
const latestStep = steps.at(-1);
const providerContextUsed =
latestStep?.inputTokens !== undefined && latestStep.outputTokens !== undefined
? latestStep.inputTokens + latestStep.outputTokens
: undefined;
const usage = {
...persistedUsage,
...(generationQuery.data?.complete && providerInputTokens !== undefined
? { inputTokens: providerInputTokens }
: {}),
...(generationQuery.data?.complete && providerOutputTokens !== undefined
? { outputTokens: providerOutputTokens }
: {}),
...(generationQuery.data?.complete && providerInputTokens !== undefined && providerOutputTokens !== undefined
? { totalTokens: providerInputTokens + providerOutputTokens }
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.cachedInputTokens) !== undefined
? { cachedInputTokens: sumCompleteMetric((step) => step.cachedInputTokens) }
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.reasoningTokens) !== undefined
? { reasoningTokens: sumCompleteMetric((step) => step.reasoningTokens) }
: {}),
};
const telemetry = {
...persistedTelemetry,
steps,
...(generationQuery.data?.complete && providerGenerationTimeMs !== undefined
? { generationTimeMs: providerGenerationTimeMs }
: {}),
...(generationQuery.data?.complete && providerOutputTokensPerSecond !== undefined
? { providerOutputTokensPerSecond }
: {}),
...(generationQuery.data?.complete && providerContextUsed !== undefined
? {
contextUsed: providerContextUsed,
...(persistedTelemetry.contextLength
? { contextUtilization: Math.min(providerContextUsed / persistedTelemetry.contextLength, 1) }
: {}),
}
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.cost) !== undefined
? { cost: sumCompleteMetric((step) => step.cost), costType: "reported" as const }
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.upstreamCost) !== undefined
? { upstreamCost: sumCompleteMetric((step) => step.upstreamCost) }
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.cacheDiscount) !== undefined
? { cacheDiscount: sumCompleteMetric((step) => step.cacheDiscount) }
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.fallbackCount) !== undefined
? { fallbackCount: sumCompleteMetric((step) => step.fallbackCount) }
: {}),
...(generationQuery.data?.complete && sumCompleteMetric((step) => step.fallbackLatencyMs) !== undefined
? { fallbackLatencyMs: sumCompleteMetric((step) => step.fallbackLatencyMs) }
: {}),
};
const contextLength = telemetry.contextLength ?? 0;
const contextUsed = telemetry.contextUsed ?? 0;
@@ -645,6 +737,21 @@ const RequestTelemetry = () => {
const contextLabel = hasContextWindow
? `${t("usage.contextWindow")}: ${contextUsed.toLocaleString()} / ${contextLength.toLocaleString()} (${contextPercentage}%)`
: `${t("usage.contextWindow")}: ${t("usage.notReported")}`;
const displayedThroughput = telemetry.providerOutputTokensPerSecond ?? telemetry.outputTokensPerSecond;
const hasProviderMetrics =
generationQuery.isFetching ||
telemetry.providerOutputTokensPerSecond !== undefined ||
telemetry.generationTimeMs !== undefined ||
telemetry.cacheDiscount !== undefined ||
telemetry.fallbackCount !== undefined ||
telemetry.fallbackLatencyMs !== undefined ||
telemetry.steps.some(
(step) =>
step.providerLatencyMs !== undefined ||
step.moderationLatencyMs !== undefined ||
step.serviceTier !== undefined ||
step.dataRegion !== undefined,
);
return (
<Box className={classes.telemetry}>
@@ -661,9 +768,9 @@ const RequestTelemetry = () => {
{formatDuration(telemetry.durationMs)}
</Text>
)}
{telemetry.outputTokensPerSecond !== undefined && (
{displayedThroughput !== undefined && (
<Text size="xs" c="dimmed">
{telemetry.outputTokensPerSecond.toFixed(1)} tok/s
{displayedThroughput.toFixed(1)} tok/s
</Text>
)}
{telemetry.cost !== undefined && (
@@ -676,7 +783,7 @@ const RequestTelemetry = () => {
<Popover
opened={opened}
onChange={setOpened}
width="min(20rem, calc(100vw - 1.5rem))"
width="min(23rem, calc(100vw - 1.5rem))"
position="bottom-end"
shadow="md"
withArrow
@@ -803,7 +910,18 @@ const RequestTelemetry = () => {
</Box>
<Divider />
<Group gap="lg" align="flex-start">
<Text size="xs" fw={700} tt="uppercase" c="dimmed">
{t("usage.homarrTiming")}
</Text>
<Box className={classes.usageGrid}>
<div>
<Text size="xs" c="dimmed">
{t("usage.endToEnd")}
</Text>
<Text size="sm" fw={600}>
{telemetry.durationMs !== undefined ? formatDuration(telemetry.durationMs) : t("usage.notReported")}
</Text>
</div>
<div>
<Text size="xs" c="dimmed">
{t("usage.firstOutput")}
@@ -814,6 +932,16 @@ const RequestTelemetry = () => {
: t("usage.notReported")}
</Text>
</div>
<div>
<Text size="xs" c="dimmed">
{t("usage.observedThroughput")}
</Text>
<Text size="sm" fw={600}>
{telemetry.outputTokensPerSecond !== undefined
? `${telemetry.outputTokensPerSecond.toFixed(1)} tok/s`
: t("usage.notReported")}
</Text>
</div>
<div>
<Text size="xs" c="dimmed">
{t("usage.cost")}
@@ -847,7 +975,74 @@ const RequestTelemetry = () => {
</Text>
</div>
)}
</Group>
</Box>
{hasProviderMetrics && (
<>
<Divider />
<Text size="xs" fw={700} tt="uppercase" c="dimmed">
{t("usage.providerTiming")}
</Text>
{generationQuery.isFetching && generationQuery.data?.complete !== true && (
<Group gap="xs">
<Loader size="xs" type="bars" />
<Text size="xs" c="dimmed">
{t("usage.loadingProviderDetails")}
</Text>
</Group>
)}
<Box className={classes.usageGrid}>
<div>
<Text size="xs" c="dimmed">
{t("usage.generationTime")}
</Text>
<Text size="sm" fw={600}>
{telemetry.generationTimeMs !== undefined
? formatDuration(telemetry.generationTimeMs)
: t("usage.notReported")}
</Text>
</div>
<div>
<Text size="xs" c="dimmed">
{t("usage.providerThroughput")}
</Text>
<Text size="sm" fw={600}>
{telemetry.providerOutputTokensPerSecond !== undefined
? `${telemetry.providerOutputTokensPerSecond.toFixed(1)} tok/s`
: t("usage.notReported")}
</Text>
</div>
<div>
<Text size="xs" c="dimmed">
{t("usage.fallbacks")}
</Text>
<Text size="sm" fw={600}>
{telemetry.fallbackCount?.toLocaleString() ?? t("usage.notReported")}
</Text>
</div>
{telemetry.fallbackLatencyMs !== undefined && (
<div>
<Text size="xs" c="dimmed">
{t("usage.fallbackLatency")}
</Text>
<Text size="sm" fw={600}>
{formatDuration(telemetry.fallbackLatencyMs)}
</Text>
</div>
)}
{telemetry.cacheDiscount !== undefined && (
<div>
<Text size="xs" c="dimmed">
{t("usage.cacheDiscount")}
</Text>
<Text size="sm" fw={600}>
{formatCost(telemetry.cacheDiscount)}
</Text>
</div>
)}
</Box>
</>
)}
{telemetry.steps.length > 0 && (
<Stack gap={4}>
@@ -863,12 +1058,86 @@ const RequestTelemetry = () => {
</Text>
<Text size="xs" c="dimmed">
{formatDuration(step.durationMs)}
{step.outputTokensPerSecond !== undefined
? ` · ${step.outputTokensPerSecond.toFixed(1)} tok/s`
: ""}
{step.providerOutputTokensPerSecond !== undefined
? ` · ${step.providerOutputTokensPerSecond.toFixed(1)} tok/s`
: step.outputTokensPerSecond !== undefined
? ` · ${step.outputTokensPerSecond.toFixed(1)} tok/s`
: ""}
{step.cost !== undefined ? ` · ${formatCost(step.cost)}` : ""}
</Text>
</Group>
{(step.providerLatencyMs !== undefined ||
step.generationTimeMs !== undefined ||
step.moderationLatencyMs !== undefined ||
step.fallbackCount !== undefined ||
step.fallbackLatencyMs !== undefined) && (
<Text size="xs" c="dimmed">
{[
step.providerLatencyMs !== undefined
? `${t("usage.providerLatency")} ${formatDuration(step.providerLatencyMs)}`
: undefined,
step.generationTimeMs !== undefined
? `${t("usage.generationTime")} ${formatDuration(step.generationTimeMs)}`
: undefined,
step.moderationLatencyMs !== undefined
? `${t("usage.moderationLatency")} ${formatDuration(step.moderationLatencyMs)}`
: undefined,
step.fallbackCount !== undefined
? `${t("usage.fallbacks")} ${step.fallbackCount.toLocaleString()}`
: undefined,
step.fallbackLatencyMs !== undefined
? `${t("usage.fallbackLatency")} ${formatDuration(step.fallbackLatencyMs)}`
: undefined,
]
.filter(Boolean)
.join(" · ")}
</Text>
)}
{(step.inputTokens !== undefined || step.outputTokens !== undefined) && (
<Text size="xs" c="dimmed">
{[
step.inputTokens !== undefined
? `${step.inputTokens.toLocaleString()} ${t("usage.inputShort")}`
: undefined,
step.outputTokens !== undefined
? `${step.outputTokens.toLocaleString()} ${t("usage.outputShort")}`
: undefined,
step.cachedInputTokens !== undefined
? `${step.cachedInputTokens.toLocaleString()} ${t("usage.cachedShort")}`
: undefined,
step.reasoningTokens !== undefined
? `${step.reasoningTokens.toLocaleString()} ${t("usage.reasoningShort")}`
: undefined,
]
.filter(Boolean)
.join(" · ")}
</Text>
)}
{(step.serviceTier ||
step.dataRegion ||
step.routerStrategy ||
step.routerRegion ||
step.isByok) && (
<Text size="xs" c="dimmed">
{[
step.routerStrategy,
step.routerRegion,
step.serviceTier,
step.dataRegion,
step.isByok ? t("usage.byok") : undefined,
]
.filter(Boolean)
.join(" · ")}
</Text>
)}
{(step.normalizedInputTokens !== undefined || step.normalizedOutputTokens !== undefined) &&
(step.normalizedInputTokens !== step.inputTokens ||
step.normalizedOutputTokens !== step.outputTokens) && (
<Text size="xs" c="dimmed">
{t("usage.normalizedTokens")}: {step.normalizedInputTokens?.toLocaleString() ?? "–"} /{" "}
{step.normalizedOutputTokens?.toLocaleString() ?? "–"}
</Text>
)}
{step.generationId && (
<Text size="xs" c="dimmed" className={classes.generationId} title={step.generationId}>
{t("usage.generation")}: {step.generationId}