From b8b0482d57b43c93dbb7e9331cc55ea7be907984 Mon Sep 17 00:00:00 2001 From: "andrii.dudar" Date: Tue, 17 Dec 2024 12:11:12 +0100 Subject: [PATCH] [OPIK-621] [Error tracking] [FE] Add Trace/Span error tracking support (#904) --- .../AddEditDatasetItemDialog.tsx | 4 +- .../pages/TracesPage/TracesSpansTab.tsx | 7 +++ .../shared/DataTableCells/ErrorCell.tsx | 45 ++++++++++++++++ .../BaseTraceDataTypeIcon.tsx | 2 +- .../TraceDataViewer/ErrorTab.tsx | 53 +++++++++++++++++++ .../TraceDataViewer/TraceDataViewer.tsx | 17 +++++- .../TraceTreeViewer/TraceTreeViewer.tsx | 3 ++ .../TraceTreeViewer/treeRenderers.tsx | 18 ++++++- .../opik-frontend/src/components/ui/alert.tsx | 7 +-- .../src/components/ui/tooltip.tsx | 2 +- apps/opik-frontend/src/types/traces.ts | 8 ++- 11 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 apps/opik-frontend/src/components/shared/DataTableCells/ErrorCell.tsx create mode 100644 apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/ErrorTab.tsx diff --git a/apps/opik-frontend/src/components/pages/DatasetItemsPage/AddEditDatasetItemDialog.tsx b/apps/opik-frontend/src/components/pages/DatasetItemsPage/AddEditDatasetItemDialog.tsx index a52a23df60..383b08b14f 100644 --- a/apps/opik-frontend/src/components/pages/DatasetItemsPage/AddEditDatasetItemDialog.tsx +++ b/apps/opik-frontend/src/components/pages/DatasetItemsPage/AddEditDatasetItemDialog.tsx @@ -17,7 +17,7 @@ import { DATASET_ITEM_SOURCE, DatasetItem } from "@/types/datasets"; import useAppStore from "@/store/AppStore"; import useDatasetItemBatchMutation from "@/api/datasets/useDatasetItemBatchMutation"; import { isValidJsonObject, safelyParseJSON } from "@/lib/utils"; -import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Alert, AlertTitle } from "@/components/ui/alert"; import { useCodemirrorTheme } from "@/hooks/useCodemirrorTheme"; const ERROR_TIMEOUT = 3000; @@ -107,7 +107,7 @@ const AddEditDatasetItemDialog: React.FunctionComponent< {showInvalidJSON && ( - Invalid JSON + Invalid JSON )} diff --git a/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx b/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx index 04e5210fc3..25fd70a4df 100644 --- a/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx +++ b/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx @@ -44,6 +44,7 @@ import LinkCell from "@/components/shared/DataTableCells/LinkCell"; import CodeCell from "@/components/shared/DataTableCells/CodeCell"; import ListCell from "@/components/shared/DataTableCells/ListCell"; import CostCell from "@/components/shared/DataTableCells/CostCell"; +import ErrorCell from "@/components/shared/DataTableCells/ErrorCell"; import FeedbackScoreCell from "@/components/shared/DataTableCells/FeedbackScoreCell"; import FeedbackScoreHeader from "@/components/shared/DataTableHeaders/FeedbackScoreHeader"; import TraceDetailsPanel from "@/components/shared/TraceDetailsPanel/TraceDetailsPanel"; @@ -138,6 +139,12 @@ const SHARED_COLUMNS: ColumnData[] = [ const TRACES_PAGE_COLUMNS = [ ...SHARED_COLUMNS, + { + id: "error_info", + label: "Error", + type: COLUMN_TYPE.string, + cell: ErrorCell as never, + }, { id: "created_by", label: "Created by", diff --git a/apps/opik-frontend/src/components/shared/DataTableCells/ErrorCell.tsx b/apps/opik-frontend/src/components/shared/DataTableCells/ErrorCell.tsx new file mode 100644 index 0000000000..609c748a2b --- /dev/null +++ b/apps/opik-frontend/src/components/shared/DataTableCells/ErrorCell.tsx @@ -0,0 +1,45 @@ +import { CellContext } from "@tanstack/react-table"; +import CellWrapper from "@/components/shared/DataTableCells/CellWrapper"; +import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper"; +import { ROW_HEIGHT } from "@/types/shared"; +import { BaseTraceDataErrorInfo } from "@/types/traces"; + +const ErrorCell = ( + context: CellContext, +) => { + const value = context.getValue(); + + if (!value) return null; + + const rowHeight = + context.column.columnDef.meta?.overrideRowHeight ?? + context.table.options.meta?.rowHeight ?? + ROW_HEIGHT.small; + + const isSmall = rowHeight === ROW_HEIGHT.small; + + return ( + + + {isSmall ? ( + {value.exception_type} + ) : ( +
+ {value.exception_type} +
+ )} +
+
+ ); +}; + +export default ErrorCell; diff --git a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/BaseTraceDataTypeIcon.tsx b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/BaseTraceDataTypeIcon.tsx index 7b3d90241a..fc093ba293 100644 --- a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/BaseTraceDataTypeIcon.tsx +++ b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/BaseTraceDataTypeIcon.tsx @@ -45,7 +45,7 @@ const BaseTraceDataTypeIcon: React.FunctionComponent<
diff --git a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/ErrorTab.tsx b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/ErrorTab.tsx new file mode 100644 index 0000000000..c1975216d4 --- /dev/null +++ b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/ErrorTab.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { Span, Trace } from "@/types/traces"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; + +type ErrorTabProps = { + data: Trace | Span; +}; + +const ErrorTab: React.FunctionComponent = ({ data }) => { + const error = data.error_info!; + + return ( + + + Exception type + +
+ {error.exception_type} +
+
+
+ {error.message && ( + + Message + +
+ {error.message} +
+
+
+ )} + + Traceback + +
+ {error.traceback} +
+
+
+
+ ); +}; + +export default ErrorTab; diff --git a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/TraceDataViewer.tsx b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/TraceDataViewer.tsx index 8cf80f7752..6f6dbd6f97 100644 --- a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/TraceDataViewer.tsx +++ b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceDataViewer/TraceDataViewer.tsx @@ -19,6 +19,7 @@ import InputOutputTab from "./InputOutputTab"; import MetadataTab from "./MatadataTab"; import FeedbackScoreTab from "./FeedbackScoreTab"; import AgentGraphTab from "./AgentGraphTab"; +import ErrorTab from "./ErrorTab"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; import { calcDuration, millisecondsToSeconds } from "@/lib/utils"; @@ -58,12 +59,16 @@ const TraceDataViewer: React.FunctionComponent = ({ null, ); const hasAgentGraph = Boolean(agentGraphData); + const hasError = Boolean(data.error_info); const [tab = "input", setTab] = useQueryParam("traceTab", StringParam, { updateType: "replaceIn", }); - const selectedTab = tab === "graph" && !hasAgentGraph ? "input" : tab; + const selectedTab = + (tab === "graph" && !hasAgentGraph) || (tab === "error" && !hasError) + ? "input" + : tab; const copyClickHandler = useCallback(() => { toast({ @@ -174,6 +179,11 @@ const TraceDataViewer: React.FunctionComponent = ({ Agent graph )} + {hasError && ( + + Error + + )} @@ -189,6 +199,11 @@ const TraceDataViewer: React.FunctionComponent = ({ )} + {hasError && ( + + + + )}
diff --git a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/TraceTreeViewer.tsx b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/TraceTreeViewer.tsx index 67d9054ac9..50d3dce47c 100644 --- a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/TraceTreeViewer.tsx +++ b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/TraceTreeViewer.tsx @@ -21,6 +21,7 @@ type SpanWithMetadata = Omit & { maxStartTime?: number; maxEndTime?: number; maxDuration?: number; + hasError?: boolean; }; type TreeData = Record>; @@ -120,6 +121,7 @@ const TraceTreeViewer: React.FunctionComponent = ({ tokens: trace.usage?.total_tokens, duration: calcDuration(trace.start_time, trace.end_time), name: trace.name, + hasError: Boolean(trace.error_info), }, }, }; @@ -140,6 +142,7 @@ const TraceTreeViewer: React.FunctionComponent = ({ tokens: span.usage?.total_tokens, duration: calcDuration(span.start_time, span.end_time), startTimestamp: new Date(span.start_time).getTime(), + hasError: Boolean(span.error_info), }, isFolder: true, index: span.id, diff --git a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/treeRenderers.tsx b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/treeRenderers.tsx index f6ae674c17..1c1a3d0d71 100644 --- a/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/treeRenderers.tsx +++ b/apps/opik-frontend/src/components/shared/TraceDetailsPanel/TraceTreeViewer/treeRenderers.tsx @@ -4,13 +4,20 @@ import isNumber from "lodash/isNumber"; import isNull from "lodash/isNull"; import isUndefined from "lodash/isUndefined"; import { TreeRenderProps } from "react-complex-tree"; -import { ChevronRight, Clock, Coins, Hash, PenLine } from "lucide-react"; +import { + ChevronRight, + CircleAlert, + Clock, + Coins, + Hash, + PenLine, +} from "lucide-react"; import { cn, millisecondsToSeconds } from "@/lib/utils"; import { BASE_TRACE_DATA_TYPE } from "@/types/traces"; +import { formatCost } from "@/lib/money"; import BaseTraceDataTypeIcon from "../BaseTraceDataTypeIcon"; import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper"; import styles from "./TraceTreeViewer.module.scss"; -import { formatCost } from "@/lib/money"; const generateStubCells = (depth: number) => { const items = []; @@ -106,6 +113,13 @@ export const treeRenderers: TreeRenderProps = { />
+ {props.item.data.hasError && ( + +
+ +
+
+ )}
{duration} diff --git a/apps/opik-frontend/src/components/ui/alert.tsx b/apps/opik-frontend/src/components/ui/alert.tsx index f6a81bf55f..e9132a7d56 100644 --- a/apps/opik-frontend/src/components/ui/alert.tsx +++ b/apps/opik-frontend/src/components/ui/alert.tsx @@ -10,7 +10,7 @@ const alertVariants = cva( variant: { default: "bg-muted text-foreground-secondary", destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + "border border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", }, }, defaultVariants: { @@ -38,10 +38,7 @@ const AlertTitle = React.forwardRef< >(({ className, ...props }, ref) => (
)); diff --git a/apps/opik-frontend/src/components/ui/tooltip.tsx b/apps/opik-frontend/src/components/ui/tooltip.tsx index eff92372d6..493bd9e8e0 100644 --- a/apps/opik-frontend/src/components/ui/tooltip.tsx +++ b/apps/opik-frontend/src/components/ui/tooltip.tsx @@ -26,7 +26,7 @@ const TooltipArrow = React.forwardRef< TooltipArrow.displayName = TooltipPrimitive.Arrow.displayName; const tooltipVariants = cva( - "comet-body-s z-50 max-w-[80vw] overflow-hidden rounded-md bg-tooltip text-tooltip-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "comet-body-s z-50 max-w-[80vw] overflow-hidden whitespace-pre-wrap rounded-md bg-tooltip text-tooltip-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", { variants: { variant: { diff --git a/apps/opik-frontend/src/types/traces.ts b/apps/opik-frontend/src/types/traces.ts index 75801e52c4..a7a40e2153 100644 --- a/apps/opik-frontend/src/types/traces.ts +++ b/apps/opik-frontend/src/types/traces.ts @@ -17,6 +17,12 @@ export interface UsageData { total_tokens: number; } +export interface BaseTraceDataErrorInfo { + exception_type: string; + message?: string; + traceback: string; +} + export interface BaseTraceData { id: string; name: string; @@ -30,8 +36,8 @@ export interface BaseTraceData { feedback_scores?: TraceFeedbackScore[]; tags: string[]; usage?: UsageData; - total_estimated_cost?: number; + error_info?: BaseTraceDataErrorInfo; } export interface Trace extends BaseTraceData {