annotations
Annotations are named labels/scores attached to spans, traces, sessions, or experiment runs by humans, code, or LLM judges. A **note** is an annotation with…
A filter condition is a Python boolean expression that Phoenix compiles to SQL. There are three filter languages, one each for spans, traces, and sessions, and the argument name picks the language. The vocabularies do not mix.
> /plugin marketplace add arize-ai/phoenix > /plugin install arize-phoenix@arize-phoenix
How it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
A filter condition is a Python boolean expression that Phoenix compiles to SQL. There are three filter languages, one each for spans, traces, and sessions, and the argument name picks the language. The vocabularies do not mix.
A filter condition is a Python boolean expression that Phoenix compiles to SQL. There are three filter languages, one each for spans, traces, and sessions, and the argument name picks the language. The vocabularies do not mix.
| Language | Argument | Keeps | Accepted on | | --- | --- | --- | --- | | Span filter | `filterCondition` | individual spans | `Project.spans`, `Trace.spans`, project aggregates (`recordCount`, `tokenCountTotal`, `costSummary`, `latencyMsQuantile`, ...), `SpanQuery().where(...)` in the Python client, the UI spans filter bar | | Trace filter | `traceFilterCondition` | every span of a matching trace | `Project.spans`, the UI traces filter bar, the `filter` query param on `GET /v1/projects/{id}/traces` | | Session filter | `sessionFilterCondition` | sessions | `Project.sessions`, project aggregates, the UI sessions filter bar |
`filterCondition` and `traceFilterCondition` compose on `Project.spans`: matching spans inside matching traces. `filterCondition` and `sessionFilterCondition` are mutually exclusive on the aggregates that accept both.
| Need | GraphQL field on `Project` | | --- | --- | | Validate before running | `validateSpanFilterCondition(condition:)`, `validateTraceFilterCondition(condition:)`, `validateSessionFilterCondition(condition:)` → `{ isValid errorMessage }` | | List bindable names | `traceFilterVocabulary`, `sessionFilterVocabulary` → `{ name type category description iterableName }` | | Span filter names | no vocabulary field; the table under [Span filter](#span-filter) is exhaustive |
A span filter that returns no rows may be misspelled rather than unmatched. Check the spelling against the vocabulary before concluding there is no data.
| Category | Accepted | Rejected | | --- | --- | --- | | Comparison | `==` `!=` `<` `<=` `>` `>=`; chained: `500 < latency_ms <= 2000` | `=` | | Missing values | `is None`, `is not None` | `is null`, `null`, `is` with any other value | | Membership | `x in [...]` exact; `'text' in field` case-insensitive substring; `not in` | `None` inside a list; a literal on the left (`1 in [1, 2]`); `like` | | Logic | `and`, `or`, `not`, parentheses | `&&`, `\|\|`, `&`, `\|`, `!` | | Arithmetic | `+` `-` `*` `/` `%` | `**`, `//`, bitwise operators | | Casts | `float(x)`, `int(x)`, `str(x)` | `bool(x)`, any other function | | Comprehensions | `any`, `all`, `len`, `sum`, `max`, `min` over a declared collection: `any(d.cost > 0 for d in cost_details)`; `len` takes a list comprehension | method calls (`name.startswith(...)`), `len(name)` |
| Kind | Write | Not | | --- | --- | --- | | String | `'LLM'` or `"LLM"` | unquoted: `LLM` | | Number | `100`, `0.5` | quoted: `'100'` | | Boolean | `True`, `False` | `true`, `false` | | Missing | `None` | `null`, `nil` | | Datetime | ISO 8601 with an offset: `'2026-09-01T00:00:00Z'`, `'2026-09-01T00:00:00+00:00'` | without an offset | | List | `['LLM', 'TOOL']`, same type throughout | mixed types; `None` elements |
matches neither `metadata['tier'] == 'premium'` nor `metadata['tier'] != 'premium'`. Spell the missing case out: `metadata['tier'] != 'premium' or metadata['tier'] is None`.
metadata['flag']` is rejected; write `metadata['flag'] == True`.
bare accessor is an existence check: `annotations['quality']`.
There is no `traces` connection and no root-span argument. Root-span scoping is a clause in `filterCondition`, and it composes with everything else. The UI's traces table is `spans(filterCondition: "parent_span is None", traceFilterCondition: ...)`.
| Clause | Keeps | Use when | | --- | --- | --- | | `parent_id is None` | spans with no parent id | the default | | `parent_span is None` | those, plus orphans whose parent span was never received | you want every top-level span, including ones whose parent was dropped |
A root span is usually one per trace. Fragmented traces can have several.
This table is exhaustive. Every identifier not in it is read as an attribute path.
| Name | Type | Notes | | --- | --- | --- | | `span_id`, `trace_id`, `parent_id` | string | OpenTelemetry hex ids | | `name` | string | span name | | `span_kind` | enum | `'CHAIN'`, `'LLM'`, `'RETRIEVER'`, `'EMBEDDING'`, `'TOOL'`, `'AGENT'`, `'RERANKER'`, `'GUARDRAIL'`, `'EVALUATOR'`, `'PROMPT'`, `'UNKNOWN'`; literals are uppercased for you | | `status_code` | enum | `'OK'`, `'ERROR'`, `'UNSET'`; literals are uppercased for you | | `status_message` | string | error text | | `latency_ms` | number | | | `start_time`, `end_time` | datetime | | | `cumulative_llm_token_count_prompt`, `cumulative_llm_token_count_completion`, `cumulative_llm_token_count_total` | number | this span plus its descendants | | `llm.token_count.prompt`, `llm.token_count.completion`, `llm.token_count.total` | number | this span alone | | `total_cost`, `prompt_cost`, `completion_cost` | number | `0` when the span has no cost row | | `cost_details` | collection | iterable only; elements have `token_type` (string), `is_prompt` (boolean), `cost`, `tokens`, `cost_per_token` (number) | | `parent_span` | reserved | only `is None` / `is not None`; `parent_span.name` is rejected | | `annotations['name']` | annotation | on the span itself; `evals['name']` is a legacy alias | | `trace_annotations['name']` | annotation | on the span's containing trace | | `a
Repo: arize-ai/phoenix
Annotations are named labels/scores attached to spans, traces, sessions, or experiment runs by humans, code, or LLM judges. A **note** is an annotation with…
There is **no `getDatasetByName`** — fetch via `node(id:) { ... on Dataset { ... } }` or the `datasets(filter: DatasetFilter, sort)` connection.
An experiment is one run of a prompt or pipeline over every example in a dataset.
- `Project.spans(first, after, timeRange, sort: SpanSort, filterCondition: String, traceFilterCondition: String)` → connection of `Span`. There is **no…
There is **no `getPromptByName`** — fetch via `node(id:)` or the `prompts(filter: PromptFilter, labelIds)` connection.
A session groups the traces of one multi-turn conversation.