diff --git a/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyDetails.tsx b/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyDetails.tsx
index 1d9289e1a39..3fa6a8bf433 100644
--- a/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyDetails.tsx
+++ b/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyDetails.tsx
@@ -3,7 +3,7 @@
import { analysisKeyInfoQuery } from "../../api/queries";
import { useQuery } from "@tanstack/react-query";
-import { useLensApi } from "../../api/useLensApi";
+import { useLensApi } from "../../services";
import { Button } from "@/components/ui/button";
import { runTime } from "../../model/format";
@@ -26,8 +26,8 @@ function budgetLabel(amount: number | null, duration?: string | null): string {
}
export function useAnalysisKeyInfo(accessToken: string, keyId?: string) {
- const apiClient = useLensApi();
- return useQuery(analysisKeyInfoQuery(apiClient, accessToken, keyId));
+ const api = useLensApi(accessToken);
+ return useQuery(analysisKeyInfoQuery(api, keyId));
}
export function AnalysisKeyDetails({
diff --git a/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyPicker.tsx b/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyPicker.tsx
index 1d1175743e5..7cbf56b85f8 100644
--- a/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyPicker.tsx
+++ b/ui/litellm-dashboard/src/components/lens/setup/worker/AnalysisKeyPicker.tsx
@@ -5,7 +5,7 @@ import { analysisKeysQuery, type Key } from "../../api/queries";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { useState } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
-import { useLensApi } from "../../api/useLensApi";
+import { useLensApi } from "../../services";
import { AnalysisKeyDetails } from "./AnalysisKeyDetails";
import type { WorkerFormInput } from "./workerSchema";
import {
@@ -20,11 +20,11 @@ import {
export function AnalysisKeyPicker({ accessToken }: { accessToken: string }) {
const { control } = useFormContext();
const value = useWatch({ control, name: "analysisKey" });
- const apiClient = useLensApi();
+ const api = useLensApi(accessToken);
const [query, setQuery] = useState("");
const [selected, setSelected] = useState(value ? { token: value } : null);
- const keyPages = useInfiniteQuery(analysisKeysQuery(apiClient, accessToken, query));
+ const keyPages = useInfiniteQuery(analysisKeysQuery(api, query));
const keys = keyPages.data?.pages.flatMap((page) => page.keys) ?? [];
const choice = keys.find((key) => key.token === value) ?? (selected?.token === value ? selected : null);
const loading = keyPages.isFetching;
diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx
index 00e47d97a8b..ccbf9748c0e 100644
--- a/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx
@@ -1,5 +1,5 @@
"use client";
-import { useLensDemo } from "@/components/lens/LensDemoContext";
+import { useTracesApi } from "@/components/lens/services";
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
import { useState } from "react";
@@ -8,7 +8,6 @@ import { AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/cva.config";
-import { agentTraceSpanCall, agentTraceSpanErrorCall } from "../../networking";
import { type KeyValue, KeyValueRows, objectEntries } from "./KeyValueRows";
import { Card, MessageCard, Section, ToolResultCard } from "./MessageCard";
import type { ErrorSource } from "./traceTree";
@@ -30,15 +29,10 @@ const errorReason = (headline: string): string => /^([A-Za-z_][\w.]*)\(/.exec(he
/** Shared lazy fetch of one span's full input / output / attributes. */
export function useSpanDetail(accessToken: string, traceId: string, spanId: string | null, traceRef?: string) {
- const demo = useLensDemo();
+ const traces = useTracesApi(accessToken);
const queryOptions: UseQueryOptions = {
queryKey: ["agentTraceSpan", traceId, traceRef, spanId, accessToken],
- queryFn: () =>
- demo
- ? demo.client.get(
- `/v1/traces/${encodeURIComponent(traceId)}/spans/${encodeURIComponent(spanId as string)}`,
- )
- : agentTraceSpanCall(accessToken, traceId, spanId as string, traceRef),
+ queryFn: () => traces.span(traceId, spanId as string, traceRef),
enabled: spanId !== null,
staleTime: Infinity,
};
@@ -153,17 +147,12 @@ interface DetailContentProps {
}
function DiagnosticContent({ accessToken, traceId, traceRef, span }: DetailContentProps) {
- const demo = useLensDemo();
+ const traces = useTracesApi(accessToken);
const [opened, setOpened] = useState(false);
const [cursor, setCursor] = useState(null);
const queryOptions: UseQueryOptions = {
queryKey: ["agentTraceSpanError", traceId, traceRef, span.span_id, accessToken, cursor],
- queryFn: () =>
- demo
- ? demo.client.get(
- `/v1/traces/${encodeURIComponent(traceId)}/spans/${encodeURIComponent(span.span_id)}/error`,
- )
- : agentTraceSpanErrorCall(accessToken, traceId, span.span_id, { traceRef, cursor }),
+ queryFn: () => traces.spanError(traceId, span.span_id, { traceRef, cursor }),
enabled: opened,
staleTime: Infinity,
gcTime: 0,
diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceConversation.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceConversation.tsx
index b0e9463556b..7b6c9eccd68 100644
--- a/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceConversation.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceConversation.tsx
@@ -5,9 +5,8 @@ import { useState } from "react";
import { ChevronRight, Wrench } from "lucide-react";
import { cn } from "@/lib/cva.config";
import { CopyButton } from "./CopyButton";
-import { useLensDemo } from "@/components/lens/LensDemoContext";
+import { useTracesApi } from "@/components/lens/services";
import { Button } from "@/components/ui/button";
-import { agentTraceSpanCall } from "../../networking";
import { buildConversation, conversationSteps, CONVERSATION_PAGE_SIZE, type ConversationItem } from "./conversation";
import { ErrorBlock } from "./DetailContent";
import { Markdown, ToolCallBlock } from "./MessageCard";
@@ -23,7 +22,7 @@ export function TraceConversation({
accessToken: string;
onOpenStep: (id: string) => void;
}) {
- const demo = useLensDemo();
+ const traces = useTracesApi(accessToken);
const [limit, setLimit] = useState(CONVERSATION_PAGE_SIZE);
const steps = conversationSteps(trace.spans);
const visible = steps.slice(0, limit);
@@ -31,12 +30,7 @@ export function TraceConversation({
const queries = useQueries({
queries: visible.map((span) => ({
queryKey: ["agentTraceSpan", traceId, traceRef, span.span_id, accessToken],
- queryFn: (): Promise =>
- demo
- ? demo.client.get(
- `/v1/traces/${encodeURIComponent(traceId)}/spans/${encodeURIComponent(span.span_id)}`,
- )
- : agentTraceSpanCall(accessToken, traceId, span.span_id, traceRef),
+ queryFn: (): Promise => traces.span(traceId, span.span_id, traceRef),
staleTime: Infinity,
retry: false,
})),
diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceDrawer.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceDrawer.tsx
index c672b9d5135..acdaef64873 100644
--- a/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceDrawer.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/TraceDrawer.tsx
@@ -1,5 +1,6 @@
"use client";
import { useLensDemo } from "@/components/lens/LensDemoContext";
+import { useTracesApi } from "@/components/lens/services";
import { useQuery } from "@tanstack/react-query";
import { ArrowLeft, Check, Copy } from "lucide-react";
@@ -11,7 +12,7 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { cn } from "@/lib/cva.config";
import { copyToClipboard } from "@/utils/dataUtils";
-import { agentTraceCall, getProxyBaseUrl } from "../../networking";
+import { getProxyBaseUrl } from "../../networking";
import { DetailPane } from "./DetailPane";
import { IdChip } from "./IdChip";
import { formatCost } from "./AgentTracesTable";
@@ -369,14 +370,11 @@ interface RunViewProps {
/** One agent run: header with totals and "Copy for agent", span tree on the left, span details on the right. */
export function RunView({ traceId, traceRef, initialSpanId, accessToken, onBack, embedded = false }: RunViewProps) {
- const demo = useLensDemo();
+ const traces = useTracesApi(accessToken);
const [view, setView] = useState("steps");
const traceQuery = useQuery({
queryKey: ["agentTrace", traceId, traceRef, accessToken],
- queryFn: () =>
- demo
- ? demo.client.get(`/v1/traces/${encodeURIComponent(traceId)}`)
- : agentTraceCall(accessToken, traceId, traceRef),
+ queryFn: () => traces.trace(traceId, traceRef),
staleTime: 30_000,
});
const trace = traceQuery.data;
diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/tracesApi.ts b/ui/litellm-dashboard/src/components/view_logs/TraceView/tracesApi.ts
new file mode 100644
index 00000000000..398fe1ec95b
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/tracesApi.ts
@@ -0,0 +1,39 @@
+import {
+ agentTraceCall,
+ agentTraceListCall,
+ agentTraceSpanCall,
+ agentTraceSpanErrorCall,
+ apiClient,
+} from "../../networking";
+import type { SpanDetail, SpanErrorPage, Trace, TracePage } from "./traceTypes";
+
+export interface TraceWindow {
+ readonly startMs: number;
+ readonly endMs: number;
+ readonly cursor?: string | null;
+}
+
+export interface TracesApi {
+ list(window: TraceWindow): Promise;
+ anyRecorded(): Promise;
+ trace(traceId: string, traceRef?: string): Promise;
+ span(traceId: string, spanId: string, traceRef?: string): Promise;
+ spanError(
+ traceId: string,
+ spanId: string,
+ options: { readonly traceRef?: string; readonly cursor?: string | null },
+ ): Promise;
+}
+
+export function liveTracesApi(accessToken: string): TracesApi {
+ return {
+ list: (window) => agentTraceListCall({ accessToken, ...window }),
+ anyRecorded: async () => {
+ const page = await apiClient.get("/v1/traces", { accessToken, query: { start_ms: 0 } });
+ return page.data.length > 0;
+ },
+ trace: (traceId, traceRef) => agentTraceCall(accessToken, traceId, traceRef),
+ span: (traceId, spanId, traceRef) => agentTraceSpanCall(accessToken, traceId, spanId, traceRef),
+ spanError: (traceId, spanId, options) => agentTraceSpanErrorCall(accessToken, traceId, spanId, options),
+ };
+}
diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/useAgentTraces.ts b/ui/litellm-dashboard/src/components/view_logs/TraceView/useAgentTraces.ts
index 46f4b4bed71..dd3bbc873b3 100644
--- a/ui/litellm-dashboard/src/components/view_logs/TraceView/useAgentTraces.ts
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/useAgentTraces.ts
@@ -1,11 +1,10 @@
-import { useLensDemo } from "@/components/lens/LensDemoContext";
+import { useTracesApi } from "@/components/lens/services";
import { useInfiniteQuery, useQuery, type UseQueryOptions } from "@tanstack/react-query";
import moment from "moment";
import { useMemo } from "react";
import { ApiError } from "@/lib/http/client";
-import { agentTraceListCall, apiClient } from "../../networking";
import { LIVE_TAIL_INTERVAL_MS } from "../log_filter_logic";
import type { TracePage, TraceSummary } from "./traceTypes";
@@ -66,20 +65,14 @@ export function useAgentTraces({
isLiveTail,
enabled,
}: UseAgentTracesOptions): AgentTracesResult {
- const demo = useLensDemo();
+ const traces = useTracesApi(accessToken);
const fetchPage = (pageParam: unknown): Promise => {
const nowMs = Date.now();
- const listOptions: Parameters[0] = {
- accessToken,
+ return traces.list({
startMs: traceWindowStartMs(startTime, endTime, isCustomDate, nowMs),
endMs: isCustomDate ? moment(endTime).valueOf() : nowMs,
cursor: pageParam as string | null,
- };
- return demo
- ? demo.client.get("/v1/traces", {
- query: { start_ms: listOptions.startMs, end_ms: listOptions.endMs, cursor: listOptions.cursor },
- })
- : agentTraceListCall(listOptions);
+ });
};
const queryOptions: Parameters>[0] = {
queryKey: ["agentTraces", accessToken, startTime, endTime, isCustomDate],
@@ -96,11 +89,11 @@ export function useAgentTraces({
};
const query = useInfiniteQuery(queryOptions);
- const traces = useMemo(() => query.data?.pages.flatMap((page) => page.data) ?? [], [query.data]);
+ const loaded = useMemo(() => query.data?.pages.flatMap((page) => page.data) ?? [], [query.data]);
const notEnabled = isTracingNotEnabled(query.error);
return {
- traces,
+ traces: loaded,
isLoading: query.isLoading,
isFetching: query.isFetching,
notEnabledDetail: notEnabled ? query.error?.message || "Agent tracing is not enabled" : null,
@@ -112,16 +105,14 @@ export function useAgentTraces({
}
export function useTraceAvailability(accessToken: string, enabled: boolean) {
- const demo = useLensDemo();
- const client = demo?.client ?? apiClient;
- const options: UseQueryOptions = {
+ const traces = useTracesApi(accessToken);
+ const options: UseQueryOptions = {
queryKey: ["trace-availability", accessToken],
- queryFn: () => client.get("/v1/traces", { accessToken, query: { start_ms: 0 } }),
- select: (page: TracePage) => page.data.length > 0,
+ queryFn: () => traces.anyRecorded(),
enabled,
retry: false,
refetchInterval: (query) =>
- query.state.data?.data.length || requiresUserAction(query.state.error) ? false : LIVE_TAIL_INTERVAL_MS,
+ query.state.data || requiresUserAction(query.state.error) ? false : LIVE_TAIL_INTERVAL_MS,
refetchOnWindowFocus: (query) => !requiresUserAction(query.state.error),
refetchOnReconnect: (query) => !requiresUserAction(query.state.error),
refetchIntervalInBackground: false,