From 6219e91c24daed559a2d1a650f3547de4b1fb3c3 Mon Sep 17 00:00:00 2001 From: Parker Stafford <52351508+Parker-Stafford@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:34:45 -0700 Subject: [PATCH] feat(annotations): migrate span eval labels to us AnnotationLabel (#4068) * feat(annotations): update evaluation labels in traces table to use AnnotationLabel * remove changes to retrieval evals * remove evaluation label * update annotation label for unclickable version * move label to annotations directory * remove trace from annotations on traces * add trace back to annotation interface * feedback * fix prop name --- .../AnnotationColorSwatch.tsx | 0 .../components/annotation/AnnotationLabel.tsx | 120 +++++++++++++ .../annotation/AnnotationTooltip.tsx | 71 ++++++++ app/src/components/annotation/index.ts | 4 + app/src/components/annotation/types.ts | 7 + .../components/experiment/AnnotationLabel.tsx | 159 ------------------ app/src/components/experiment/index.tsx | 3 +- .../example/ExampleExperimentRunsTable.tsx | 48 ++++-- .../experiment/ExperimentCompareTable.tsx | 59 +++++-- .../pages/experiments/ExperimentsTable.tsx | 4 +- app/src/pages/project/EvaluationLabel.tsx | 65 ------- app/src/pages/project/SpansTable.tsx | 20 ++- app/src/pages/project/TracesTable.tsx | 20 ++- app/src/pages/trace/TraceDetails.tsx | 21 ++- 14 files changed, 332 insertions(+), 269 deletions(-) rename app/src/components/{experiment => annotation}/AnnotationColorSwatch.tsx (100%) create mode 100644 app/src/components/annotation/AnnotationLabel.tsx create mode 100644 app/src/components/annotation/AnnotationTooltip.tsx create mode 100644 app/src/components/annotation/index.ts create mode 100644 app/src/components/annotation/types.ts delete mode 100644 app/src/components/experiment/AnnotationLabel.tsx delete mode 100644 app/src/pages/project/EvaluationLabel.tsx diff --git a/app/src/components/experiment/AnnotationColorSwatch.tsx b/app/src/components/annotation/AnnotationColorSwatch.tsx similarity index 100% rename from app/src/components/experiment/AnnotationColorSwatch.tsx rename to app/src/components/annotation/AnnotationColorSwatch.tsx diff --git a/app/src/components/annotation/AnnotationLabel.tsx b/app/src/components/annotation/AnnotationLabel.tsx new file mode 100644 index 0000000000..1eb0667715 --- /dev/null +++ b/app/src/components/annotation/AnnotationLabel.tsx @@ -0,0 +1,120 @@ +import React from "react"; +import { css } from "@emotion/react"; + +import { Flex, Icon, Icons, Text } from "@arizeai/components"; + +import { assertUnreachable } from "@phoenix/typeUtils"; +import { formatFloat } from "@phoenix/utils/numberFormatUtils"; + +import { AnnotationColorSwatch } from "./AnnotationColorSwatch"; +import { Annotation } from "./types"; + +type AnnotationDisplayPreference = "label" | "score"; + +const baseAnnotationLabelCSS = css` + border-radius: var(--ac-global-dimension-size-50); + border: 1px solid var(--ac-global-color-grey-400); + padding: var(--ac-global-dimension-size-50) + var(--ac-global-dimension-size-100); + transition: background-color 0.2s; + &:hover { + background-color: var(--ac-global-color-grey-300); + } + .ac-icon-wrap { + font-size: 12px; + } +`; + +const textCSS = css` + display: flex; + align-items: center; + .ac-text { + display: inline-block; + max-width: 9rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const getAnnotationDisplayValue = ( + annotation: Annotation, + displayPreference: AnnotationDisplayPreference +) => { + switch (displayPreference) { + case "label": + return ( + annotation.label || + (typeof annotation.score == "number" && + formatFloat(annotation.score)) || + "n/a" + ); + case "score": + return ( + (typeof annotation.score == "number" && + formatFloat(annotation.score)) || + annotation.label || + "n/a" + ); + default: + assertUnreachable(displayPreference); + } +}; + +export function AnnotationLabel({ + annotation, + onClick, + annotationDisplayPreference = "score", +}: { + annotation: Annotation; + onClick?: () => void; + /** + * The preferred value to display in the annotation label. + * If the provided value is not available, it will fallback to an available value. + * @default "score" + */ + annotationDisplayPreference?: AnnotationDisplayPreference; +}) { + const clickable = typeof onClick == "function"; + const labelValue = getAnnotationDisplayValue( + annotation, + annotationDisplayPreference + ); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + onClick && onClick(); + }} + > + + +
+ + {annotation.name} + +
+
+ {labelValue} +
+ {clickable ? } /> : null} +
+
+ ); +} diff --git a/app/src/components/annotation/AnnotationTooltip.tsx b/app/src/components/annotation/AnnotationTooltip.tsx new file mode 100644 index 0000000000..f37ded41bc --- /dev/null +++ b/app/src/components/annotation/AnnotationTooltip.tsx @@ -0,0 +1,71 @@ +import React, { ReactNode } from "react"; + +import { + Flex, + HelpTooltip, + Text, + TooltipTrigger, + TriggerWrap, + View, +} from "@arizeai/components"; + +import { floatFormatter } from "@phoenix/utils/numberFormatUtils"; + +import { Annotation } from "./types"; + +/** + * Wraps a component with a tooltip that displays information about an annotation. + */ +export function AnnotationTooltip({ + annotation, + children, + extra, +}: { + annotation: Annotation; + children: ReactNode; + extra?: ReactNode; +}) { + return ( + + {children} + + + {annotation.name} + + + + + score + + {floatFormatter(annotation.score)} + + + + label + + {annotation.label || "--"} + + + + kind + + {annotation.annotatorKind} + + + {annotation.explanation ? ( + + + + explanation + + + {annotation.explanation} + + + + ) : null} + {extra} + + + ); +} diff --git a/app/src/components/annotation/index.ts b/app/src/components/annotation/index.ts new file mode 100644 index 0000000000..5ad59f459e --- /dev/null +++ b/app/src/components/annotation/index.ts @@ -0,0 +1,4 @@ +export * from "./AnnotationColorSwatch"; +export * from "./AnnotationLabel"; +export * from "./AnnotationTooltip"; +export * from "./types"; diff --git a/app/src/components/annotation/types.ts b/app/src/components/annotation/types.ts new file mode 100644 index 0000000000..0d806e7729 --- /dev/null +++ b/app/src/components/annotation/types.ts @@ -0,0 +1,7 @@ +export interface Annotation { + name: string; + label?: string | null; + score?: number | null; + explanation?: string | null; + annotatorKind: string; +} diff --git a/app/src/components/experiment/AnnotationLabel.tsx b/app/src/components/experiment/AnnotationLabel.tsx deleted file mode 100644 index ec109f3eeb..0000000000 --- a/app/src/components/experiment/AnnotationLabel.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from "react"; -import { css } from "@emotion/react"; - -import { - Flex, - HelpTooltip, - Icon, - Icons, - Text, - TooltipTrigger, - TriggerWrap, - View, -} from "@arizeai/components"; - -import { floatFormatter, formatFloat } from "@phoenix/utils/numberFormatUtils"; - -import { AnnotationColorSwatch } from "./AnnotationColorSwatch"; - -interface Annotation { - name: string; - label?: string | null; - score?: number | null; - explanation?: string | null; - annotatorKind: string; - trace: { - traceId: string; - projectId: string; - } | null; -} - -const textCSS = css` - display: flex; - align-items: center; - .ac-text { - display: inline-block; - max-width: 9rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -`; -export function AnnotationLabel({ - annotation, - onClick, -}: { - annotation: Annotation; - onClick?: () => void; -}) { - const clickable = typeof onClick == "function"; - const labelValue = - (typeof annotation.score == "number" && formatFloat(annotation.score)) || - annotation.label || - "n/a"; - - return ( - - -
{ - e.stopPropagation(); - e.preventDefault(); - onClick && onClick(); - }} - > - - -
- - {annotation.name} - -
-
- {labelValue} -
- {annotation.trace && clickable ? ( - } /> - ) : null} -
-
-
- - - {annotation.name} - - - - - score - - {floatFormatter(annotation.score)} - - - - label - - {annotation.label || "--"} - - - - kind - - {annotation.annotatorKind} - - - {annotation.explanation ? ( - - - - explanation - - - {annotation.explanation} - - - - ) : null} - {annotation.trace && clickable ? ( - -
- } /> - Click to view evaluator trace -
-
- ) : null} -
-
- ); -} diff --git a/app/src/components/experiment/index.tsx b/app/src/components/experiment/index.tsx index e4a11ebf48..1902ab4b9d 100644 --- a/app/src/components/experiment/index.tsx +++ b/app/src/components/experiment/index.tsx @@ -1,2 +1 @@ -export * from "./AnnotationLabel"; -export * from "./AnnotationColorSwatch"; +export * from "./SequenceNumberLabel"; diff --git a/app/src/pages/example/ExampleExperimentRunsTable.tsx b/app/src/pages/example/ExampleExperimentRunsTable.tsx index 441786885b..11da21c57b 100644 --- a/app/src/pages/example/ExampleExperimentRunsTable.tsx +++ b/app/src/pages/example/ExampleExperimentRunsTable.tsx @@ -9,9 +9,12 @@ import { } from "@tanstack/react-table"; import { css } from "@emotion/react"; -import { Button, Flex, Icon, Icons, Text } from "@arizeai/components"; +import { Button, Flex, Icon, Icons, Text, View } from "@arizeai/components"; -import { AnnotationLabel } from "@phoenix/components/experiment"; +import { + AnnotationLabel, + AnnotationTooltip, +} from "@phoenix/components/annotation"; import { selectableTableCSS } from "@phoenix/components/table/styles"; import { TextCell } from "@phoenix/components/table/TextCell"; import { TimestampCell } from "@phoenix/components/table/TimestampCell"; @@ -40,6 +43,14 @@ export function ExampleExperimentsTableEmpty() { ); } +const annotationTooltipExtraCSS = css` + display: flex; + flex-direction: row; + align-items: center; + color: var(--ac-global-color-primary); + gap: var(--ac-global-dimension-size-50); +`; + export function ExampleExperimentRunsTable({ example, }: { @@ -151,17 +162,32 @@ export function ExampleExperimentRunsTable({ {row.original.annotations.edges.map((annotationEdge, index) => { const annotation = annotationEdge.annotation; return ( - { - if (annotation.trace) { - navigate( - `/projects/${annotation.trace.projectId}/traces/${annotation.trace.traceId}` - ); - } - }} - /> + extra={ + annotation.trace && ( + +
+ } /> + Click to view evaluator trace +
+
+ ) + } + > + { + if (annotation.trace) { + navigate( + `/projects/${annotation.trace.projectId}/traces/${annotation.trace.traceId}` + ); + } + }} + /> + ); })} diff --git a/app/src/pages/experiment/ExperimentCompareTable.tsx b/app/src/pages/experiment/ExperimentCompareTable.tsx index ab6bd7ebe9..9581695453 100644 --- a/app/src/pages/experiment/ExperimentCompareTable.tsx +++ b/app/src/pages/experiment/ExperimentCompareTable.tsx @@ -38,9 +38,12 @@ import { } from "@arizeai/components"; import { CopyToClipboardButton, ViewSummaryAside } from "@phoenix/components"; +import { + AnnotationLabel, + AnnotationTooltip, +} from "@phoenix/components/annotation"; import { JSONBlock } from "@phoenix/components/code"; import { JSONText } from "@phoenix/components/code/JSONText"; -import { AnnotationLabel } from "@phoenix/components/experiment"; import { SequenceNumberLabel } from "@phoenix/components/experiment/SequenceNumberLabel"; import { resizeHandleCSS } from "@phoenix/components/resize"; import { CompactJSONCell } from "@phoenix/components/table"; @@ -134,6 +137,14 @@ const cellControlsCSS = css` gap: var(--ac-global-dimension-static-size-100); `; +const annotationTooltipExtraCSS = css` + display: flex; + flex-direction: row; + align-items: center; + color: var(--ac-global-color-primary); + gap: var(--ac-global-dimension-size-50); +`; + export function ExperimentCompareTable(props: ExampleCompareTableProps) { const { datasetId, experimentIds, displayFullText } = props; const data = useLazyLoadQuery( @@ -613,23 +624,37 @@ function ExperimentRunOutput( {annotationsList.map((annotation) => (
  • - { - const trace = annotation.trace; - if (trace) { - startTransition(() => { - setDialog( - - ); - }); - } - }} - /> + extra={ + annotation.trace && ( + +
    + } /> + Click to view evaluator trace +
    +
    + ) + } + > + { + const trace = annotation.trace; + if (trace) { + startTransition(() => { + setDialog( + + ); + }); + } + }} + /> +
  • ))} diff --git a/app/src/pages/experiments/ExperimentsTable.tsx b/app/src/pages/experiments/ExperimentsTable.tsx index 1979678d19..e5d560449e 100644 --- a/app/src/pages/experiments/ExperimentsTable.tsx +++ b/app/src/pages/experiments/ExperimentsTable.tsx @@ -32,9 +32,9 @@ import { View, } from "@arizeai/components"; +import { AnnotationColorSwatch } from "@phoenix/components/annotation"; import { JSONBlock } from "@phoenix/components/code"; -import { AnnotationColorSwatch } from "@phoenix/components/experiment"; -import { SequenceNumberLabel } from "@phoenix/components/experiment/SequenceNumberLabel"; +import { SequenceNumberLabel } from "@phoenix/components/experiment"; import { Link } from "@phoenix/components/Link"; import { CompactJSONCell, IntCell } from "@phoenix/components/table"; import { IndeterminateCheckboxCell } from "@phoenix/components/table/IndeterminateCheckboxCell"; diff --git a/app/src/pages/project/EvaluationLabel.tsx b/app/src/pages/project/EvaluationLabel.tsx deleted file mode 100644 index 84d4522dfe..0000000000 --- a/app/src/pages/project/EvaluationLabel.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import { css } from "@emotion/react"; - -import { - Flex, - Label, - Text, - Tooltip, - TooltipTrigger, - TriggerWrap, -} from "@arizeai/components"; - -import { formatFloat } from "@phoenix/utils/numberFormatUtils"; - -interface Evaluation { - name: string; - label?: string | null; - score?: number | null; -} - -const textCSS = css` - display: flex; - align-items: center; - .ac-text { - display: inline-block; - max-width: 9rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -`; -export function EvaluationLabel({ evaluation }: { evaluation: Evaluation }) { - const labelValue = - evaluation.label || - (typeof evaluation.score == "number" && formatFloat(evaluation.score)) || - "n/a"; - return ( - - - - - - - - {evaluation.name} - - - {labelValue} - - - - - ); -} diff --git a/app/src/pages/project/SpansTable.tsx b/app/src/pages/project/SpansTable.tsx index 8380ceb9c6..2328d6664f 100644 --- a/app/src/pages/project/SpansTable.tsx +++ b/app/src/pages/project/SpansTable.tsx @@ -20,6 +20,10 @@ import { css } from "@emotion/react"; import { Flex, Icon, Icons, View } from "@arizeai/components"; +import { + AnnotationLabel, + AnnotationTooltip, +} from "@phoenix/components/annotation"; import { Link } from "@phoenix/components/Link"; import { IndeterminateCheckboxCell } from "@phoenix/components/table/IndeterminateCheckboxCell"; import { selectableTableCSS } from "@phoenix/components/table/styles"; @@ -36,7 +40,6 @@ import { SpanStatusCode, } from "./__generated__/SpansTable_spans.graphql"; import { SpansTableSpansQuery } from "./__generated__/SpansTableSpansQuery.graphql"; -import { EvaluationLabel } from "./EvaluationLabel"; import { ProjectTableEmpty } from "./ProjectTableEmpty"; import { RetrievalEvaluationLabel } from "./RetrievalEvaluationLabel"; import { SpanColumnSelector } from "./SpanColumnSelector"; @@ -189,11 +192,20 @@ export function SpansTable(props: SpansTableProps) { return ( {row.original.spanEvaluations.map((evaluation) => { + const annotation = { + ...evaluation, + annotatorKind: "LLM", + }; return ( - + annotation={annotation} + > + + ); })} {row.original.documentRetrievalMetrics.map((retrievalMetric) => { diff --git a/app/src/pages/project/TracesTable.tsx b/app/src/pages/project/TracesTable.tsx index 9a3b031648..8960d41ffa 100644 --- a/app/src/pages/project/TracesTable.tsx +++ b/app/src/pages/project/TracesTable.tsx @@ -24,6 +24,10 @@ import { css } from "@emotion/react"; import { Flex, Icon, Icons, View } from "@arizeai/components"; +import { + AnnotationLabel, + AnnotationTooltip, +} from "@phoenix/components/annotation"; import { Link } from "@phoenix/components/Link"; import { TextCell } from "@phoenix/components/table"; import { IndeterminateCheckboxCell } from "@phoenix/components/table/IndeterminateCheckboxCell"; @@ -43,7 +47,6 @@ import { TracesTable_spans$key, } from "./__generated__/TracesTable_spans.graphql"; import { TracesTableQuery } from "./__generated__/TracesTableQuery.graphql"; -import { EvaluationLabel } from "./EvaluationLabel"; import { ProjectTableEmpty } from "./ProjectTableEmpty"; import { RetrievalEvaluationLabel } from "./RetrievalEvaluationLabel"; import { SpanColumnSelector } from "./SpanColumnSelector"; @@ -263,11 +266,20 @@ export function TracesTable(props: TracesTableProps) { return ( {row.original.spanEvaluations.map((evaluation) => { + const annotation = { + ...evaluation, + annotatorKind: "LLM", + }; return ( - + annotation={annotation} + > + + ); })} {row.original.documentRetrievalMetrics.map((retrievalMetric) => { diff --git a/app/src/pages/trace/TraceDetails.tsx b/app/src/pages/trace/TraceDetails.tsx index f53070cbca..d811a74315 100644 --- a/app/src/pages/trace/TraceDetails.tsx +++ b/app/src/pages/trace/TraceDetails.tsx @@ -7,14 +7,16 @@ import { css } from "@emotion/react"; import { Flex, Text, View } from "@arizeai/components"; import { Loading } from "@phoenix/components"; +import { + AnnotationLabel, + AnnotationTooltip, +} from "@phoenix/components/annotation"; import { resizeHandleCSS } from "@phoenix/components/resize"; import { LatencyText } from "@phoenix/components/trace/LatencyText"; import { SpanStatusCodeIcon } from "@phoenix/components/trace/SpanStatusCodeIcon"; import { TraceTree } from "@phoenix/components/trace/TraceTree"; import { useSpanStatusCodeColor } from "@phoenix/components/trace/useSpanStatusCodeColor"; -import { EvaluationLabel } from "../project/EvaluationLabel"; - import { TraceDetailsQuery, TraceDetailsQuery$data, @@ -214,11 +216,20 @@ function TraceHeader({ rootSpan }: { rootSpan: Span | null }) { {spanEvaluations.map((evaluation) => { + const annotation = { + ...evaluation, + annotatorKind: "LLM", + }; return ( - + annotation={annotation} + > + + ); })}