Skip to content

Commit

Permalink
[OPIK-621] [Error tracking] [FE] Add Trace/Span error tracking support (
Browse files Browse the repository at this point in the history
  • Loading branch information
andriidudar authored Dec 17, 2024
1 parent 489d288 commit b8b0482
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -107,7 +107,7 @@ const AddEditDatasetItemDialog: React.FunctionComponent<
</div>
{showInvalidJSON && (
<Alert variant="destructive">
<AlertDescription>Invalid JSON</AlertDescription>
<AlertTitle>Invalid JSON</AlertTitle>
</Alert>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -138,6 +139,12 @@ const SHARED_COLUMNS: ColumnData<BaseTraceData>[] = [

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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = <TData,>(
context: CellContext<TData, BaseTraceDataErrorInfo | undefined>,
) => {
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 (
<CellWrapper
metadata={context.column.columnDef.meta}
tableMetadata={context.table.options.meta}
>
<TooltipWrapper
content={
value.message
? `Message: ${value.message}`
: "Error message is not specified"
}
>
{isSmall ? (
<span className="truncate">{value.exception_type}</span>
) : (
<div className="size-full overflow-y-auto">
{value.exception_type}
</div>
)}
</TooltipWrapper>
</CellWrapper>
);
};

export default ErrorCell;
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const BaseTraceDataTypeIcon: React.FunctionComponent<
<div
style={{ background: data.bg, color: data.color }}
className={cn(
"flex size-[22px] items-center justify-center rounded-md flex-shrink-0",
"relative flex size-[22px] items-center justify-center rounded-md flex-shrink-0",
)}
>
<TooltipWrapper content={data.tooltip}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ErrorTabProps> = ({ data }) => {
const error = data.error_info!;

return (
<Accordion
type="multiple"
className="w-full"
defaultValue={["type", "message", "traceback"]}
>
<AccordionItem value="type">
<AccordionTrigger>Exception type</AccordionTrigger>
<AccordionContent>
<div className="whitespace-pre-wrap break-words rounded-md bg-primary-foreground px-4 py-2">
{error.exception_type}
</div>
</AccordionContent>
</AccordionItem>
{error.message && (
<AccordionItem value="message">
<AccordionTrigger>Message</AccordionTrigger>
<AccordionContent>
<div className="whitespace-pre-wrap break-words rounded-md bg-primary-foreground px-4 py-2">
{error.message}
</div>
</AccordionContent>
</AccordionItem>
)}
<AccordionItem value="traceback">
<AccordionTrigger>Traceback</AccordionTrigger>
<AccordionContent>
<div className="whitespace-pre-wrap break-words rounded-md bg-primary-foreground px-4 py-2">
{error.traceback}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
);
};

export default ErrorTab;
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -58,12 +59,16 @@ const TraceDataViewer: React.FunctionComponent<TraceDataViewerProps> = ({
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({
Expand Down Expand Up @@ -174,6 +179,11 @@ const TraceDataViewer: React.FunctionComponent<TraceDataViewerProps> = ({
Agent graph
</TabsTrigger>
)}
{hasError && (
<TabsTrigger variant="underline" value="error">
Error
</TabsTrigger>
)}
</TabsList>
<TabsContent value="input">
<InputOutputTab data={data} />
Expand All @@ -189,6 +199,11 @@ const TraceDataViewer: React.FunctionComponent<TraceDataViewerProps> = ({
<AgentGraphTab data={agentGraphData} />
</TabsContent>
)}
{hasError && (
<TabsContent value="error">
<ErrorTab data={data} />
</TabsContent>
)}
</Tabs>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type SpanWithMetadata = Omit<Span, "type"> & {
maxStartTime?: number;
maxEndTime?: number;
maxDuration?: number;
hasError?: boolean;
};

type TreeData = Record<string, TreeItem<SpanWithMetadata>>;
Expand Down Expand Up @@ -120,6 +121,7 @@ const TraceTreeViewer: React.FunctionComponent<TraceTreeViewerProps> = ({
tokens: trace.usage?.total_tokens,
duration: calcDuration(trace.start_time, trace.end_time),
name: trace.name,
hasError: Boolean(trace.error_info),
},
},
};
Expand All @@ -140,6 +142,7 @@ const TraceTreeViewer: React.FunctionComponent<TraceTreeViewerProps> = ({
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -106,6 +113,13 @@ export const treeRenderers: TreeRenderProps = {
/>
</div>
<div className={styles.chainSpanDetails}>
{props.item.data.hasError && (
<TooltipWrapper content="Has error">
<div className={styles.chainSpanDetailsItem}>
<CircleAlert className="text-destructive" />
</div>
</TooltipWrapper>
)}
<TooltipWrapper content="Duration in seconds">
<div className={styles.chainSpanDetailsItem}>
<Clock /> {duration}
Expand Down
7 changes: 2 additions & 5 deletions apps/opik-frontend/src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -38,10 +38,7 @@ const AlertTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn(
"mb-1 comet-body-s-accented text-foreground-secondary",
className,
)}
className={cn("mb-1 comet-body-s-accented", className)}
{...props}
/>
));
Expand Down
2 changes: 1 addition & 1 deletion apps/opik-frontend/src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
8 changes: 7 additions & 1 deletion apps/opik-frontend/src/types/traces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down

0 comments on commit b8b0482

Please sign in to comment.