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}
+ >
+
+
);
})}