Skip to main content
Span annotations attach structured feedback to individual traced operations — an LLM call, a tool invocation, a retrieval step. Use them to record quality scores, human labels, or LLM-as-judge verdicts against any span in your project. All functions are imported from @arizeai/phoenix-client/spans. See Annotations for the shared annotation model and concepts.

Relevant Source Files

  • src/spans/addSpanAnnotation.ts for the single-annotation API
  • src/spans/logSpanAnnotations.ts for batch logging
  • src/spans/getSpanAnnotations.ts for reading annotations back
  • src/spans/getSpans.ts for fetching span IDs from Phoenix
  • src/spans/types.ts for the SpanAnnotation interface

Getting The spanId

spanId is the OpenTelemetry span ID for the operation you want to annotate. In practice, you usually get it one of two ways:

From Running Code With @arizeai/phoenix-otel

If you are already tracing the operation at runtime, capture the span ID from the active span and keep it with the application response, evaluation job, or feedback event that will log the annotation later.
import { register, trace } from "@arizeai/phoenix-otel";

register({ projectName: "support-bot" });

const tracer = trace.getTracer("support-bot");

const result = await tracer.startActiveSpan("answer-question", async (span) => {
  const spanId = span.spanContext().spanId;

  return {
    answer: "Phoenix is open source.",
    spanId,
  };
});
If you are already inside traced code and just need the current span, trace.getActiveSpan()?.spanContext().spanId gives you the same OpenTelemetry ID.

From Retrieved Spans During Evaluation

If you are annotating spans after the fact, fetch the spans from Phoenix first and read the OpenTelemetry span ID from span.context.span_id.
import { getSpans } from "@arizeai/phoenix-client/spans";

const { spans } = await getSpans({
  project: { projectName: "support-bot" },
  spanKind: "LLM",
  limit: 10,
});

for (const span of spans) {
  const spanId = span.context.span_id;
  console.log(span.name, spanId);
}

Add A Single Span Annotation

Use addSpanAnnotation to attach one annotation to a span. This example records an LLM-as-judge groundedness evaluation:
import { addSpanAnnotation } from "@arizeai/phoenix-client/spans";

await addSpanAnnotation({
  spanAnnotation: {
    spanId: "abc123def456",
    name: "groundedness",
    annotatorKind: "LLM",
    score: 1,
    label: "grounded",
    explanation: "Answer stayed within retrieved context.",
  },
});

Batch Log Span Annotations

Use logSpanAnnotations to send multiple annotations in a single request. This is ideal for nightly evaluation pipelines that score many spans at once:
import { logSpanAnnotations } from "@arizeai/phoenix-client/spans";

await logSpanAnnotations({
  spanAnnotations: [
    {
      spanId: "abc123def456",
      name: "helpfulness",
      annotatorKind: "CODE",
      score: 0.2,
      label: "poor",
    },
    {
      spanId: "789ghi012jkl",
      name: "helpfulness",
      annotatorKind: "CODE",
      score: 0.9,
      label: "excellent",
    },
  ],
});

Human Feedback (Thumbs Up/Down)

The most common annotation pattern: capture end-user reactions and log them as HUMAN annotations. Include metadata to correlate feedback with your application’s user model:
import { addSpanAnnotation } from "@arizeai/phoenix-client/spans";

// User clicked thumbs-up on an LLM response
await addSpanAnnotation({
  spanAnnotation: {
    spanId: responseSpanId,
    name: "user-feedback",
    annotatorKind: "HUMAN",
    label: "positive",
    score: 1,
    metadata: { userId: "u_42", channel: "web-chat" },
  },
});

Idempotent Upserts With identifier

Annotations are unique by (name, spanId, identifier). The identifier field controls whether a write creates a new annotation or updates an existing one. Without identifier, a span can only have one annotation per name — writing again overwrites it. Adding an identifier lets you store multiple annotations with the same name on the same span, each keyed by a different identifier. Re-sending the same (name, spanId, identifier) tuple updates that specific annotation in place. Common patterns:
  • One annotation per user — use a user ID as the identifier so each user’s feedback is stored separately and re-submitting updates their previous rating.
  • One annotation per evaluator version — use the evaluator version string so pipeline reruns update in place rather than duplicating.
  • One annotation per reviewer — use the reviewer’s name or ID so multiple reviewers can each annotate the same span independently.
import { addSpanAnnotation } from "@arizeai/phoenix-client/spans";

// Two different users can each leave a "helpfulness" rating on the same span
await addSpanAnnotation({
  spanAnnotation: {
    spanId: "abc123def456",
    name: "helpfulness",
    annotatorKind: "HUMAN",
    score: 1,
    label: "helpful",
    identifier: "user-alice",
  },
});

await addSpanAnnotation({
  spanAnnotation: {
    spanId: "abc123def456",
    name: "helpfulness",
    annotatorKind: "HUMAN",
    score: 0,
    label: "not-helpful",
    identifier: "user-bob",
  },
});
// Both annotations coexist. If Alice re-submits, her annotation is updated.

Read Back Span Annotations

Use getSpanAnnotations to retrieve annotations for one or more spans.

Basic Read

import { getSpanAnnotations } from "@arizeai/phoenix-client/spans";

const result = await getSpanAnnotations({
  project: { projectName: "support-bot" },
  spanIds: ["abc123def456"],
});

for (const annotation of result.annotations) {
  console.log(annotation.name, annotation.result?.label);
}

Filtered Read

Fetch only specific annotation names across multiple spans:
const result = await getSpanAnnotations({
  project: { projectName: "support-bot" },
  spanIds: ["abc123def456", "789ghi012jkl"],
  includeAnnotationNames: ["groundedness", "helpfulness"],
  limit: 50,
});
Use excludeAnnotationNames to omit specific names instead — for example, to skip "note" annotations.

Pagination

For large result sets, use cursor-based pagination:
let cursor: string | undefined;
do {
  const page = await getSpanAnnotations({
    project: { projectName: "support-bot" },
    spanIds: spanIdBatch,
    cursor,
    limit: 100,
  });
  for (const annotation of page.annotations) {
    // process each annotation
  }
  cursor = page.nextCursor ?? undefined;
} while (cursor);

Span Notes

addSpanNote attaches a free-text comment to a span. Unlike structured annotations (which are keyed by name + identifier), notes are append-only — each call creates a new note with a unique timestamp-based identifier, so multiple notes naturally accumulate on the same span. This makes notes a good mechanism for open coding: reviewers can leave qualitative observations on spans during exploratory analysis without needing to define annotation names or scoring rubrics up front. The accumulated notes can later inform what structured annotations to create.
import { addSpanNote } from "@arizeai/phoenix-client/spans";

await addSpanNote({
  spanNote: {
    spanId: "abc123def456",
    note: "Escalated: retrieval returned empty docs.",
  },
});

Parameter Reference

SpanAnnotation

FieldTypeRequiredDescription
spanIdstringYesOpenTelemetry span ID (hex, no 0x prefix)
namestringYesAnnotation name (e.g. "groundedness")
annotatorKind"HUMAN" | "LLM" | "CODE"NoDefaults to "HUMAN"
labelstringNo*Categorical label
scorenumberNo*Numeric score
explanationstringNo*Free-text explanation
identifierstringNoFor idempotent upserts
metadataRecord<string, unknown>NoArbitrary metadata
*At least one of label, score, or explanation is required.

getSpanAnnotations Options

FieldTypeRequiredDescription
project{ projectName } | { projectId }YesProject selector
spanIdsstring[]YesSpan IDs to fetch annotations for
includeAnnotationNamesstring[]NoOnly return these annotation names
excludeAnnotationNamesstring[]NoExclude these annotation names
cursorstringNoPagination cursor
limitnumberNoMax results per page (default 100)

Source Map

  • src/spans/addSpanAnnotation.ts
  • src/spans/logSpanAnnotations.ts
  • src/spans/getSpanAnnotations.ts
  • src/spans/getSpans.ts
  • src/spans/addSpanNote.ts
  • src/spans/types.ts
  • src/types/annotations.ts