DSL Filter Autocompletion#
Phoenix's span and trace filter bars accept Python-like DSL expressions (e.g. span_kind == 'LLM'). A CodeMirror-based autocomplete system powers typeahead suggestions, token-aware cursor handling, async server-side validation, and pluggable completion sources across all filter surfaces. The system was refactored in PR #14053 to extract a single reusable DSLFilterConditionField component from previously duplicated implementations, and hardened in PR #14057 with context-aware tokenization and semantic convention completions.
Key Files#
| File | Purpose |
|---|---|
DSLFilterConditionField.tsx | Generic reusable filter input component |
dslFilterConditionFieldUtils.ts | Tokenizer, string-suppression logic, and completion source factory |
annotationCompletions.ts | Expands annotation/eval names into .label, .score, .explanation completions |
SpanFilterConditionField.tsx | Span-specific thin wrapper (wires vocabulary, Relay queries, semantic convention sources) |
spanFilterSemanticConventionCompletions.ts | OpenInference semantic convention field and enum-value completions |
SpanFilterConditionContext.tsx | React context/provider holding the shared filter string state |
Architecture#
The SpanFilterConditionField wrapper assembles the following layers:
SpanFilterConditionField
├── DSLFilterConditionField (generic, @phoenix/components/filter)
│ ├── CodeMirror editor (Python syntax highlight)
│ ├── createDSLFilterCompletionSource (static vocab + snippets)
│ ├── createDSLFilterCompletionSource (loadCompletions → annotation names)
│ └── completionSources[] (external, injected before built-in sources)
├── openInferenceAttributeValueCompletionSource ← spanFilterSemanticConventionCompletions
├── openInferenceAttributeCompletions (as completions[]) ← spanFilterSemanticConventionCompletions
└── validateSpanFilterCondition (Relay GraphQL)
DSLFilterConditionField — Core Component#
DSLFilterConditionField is the generic controlled input at app/src/components/filter/DSLFilterConditionField.tsx. Its public API :
completions: Completion[]— static DSL vocabulary (fields), grouped under a "Fields" section at rank 3.snippets?: DSLFilterSnippet[]— example expressions using${placeholder}for tab-stop fields, surfaced as a "Suggestions" group at rank 1.loadCompletions?: () => Promise<Completion[]>— async dynamic vocabulary (e.g. annotation names from the server). The result is cached for the duration of a focus session and invalidated on the next focus, so freshly created names appear when the user returns to the filter.completionSources?: CompletionSource[]— additional CodeMirrorCompletionSourceinstances injected into theoverridearray before the built-in sources . Used to plug in enum-value completions that need editor state.validateCondition— async validation injected by the caller. Empty/whitespace-only conditions resolve immediately as valid without hitting the server.onValidCondition— fires insidestartTransitionafter successful validation.
Browse caps prevent a long snippet list from burying field completions when the field is empty: up to 5 snippets (MAX_BROWSE_SUGGESTIONS) and 20 fields (MAX_BROWSE_FIELDS) are shown during browse, while the full lists surface via fuzzy matching once the user types .
Validation is debounced at 250ms with stale-cancellation via an isCancelled flag. Error UX is an inline danger badge with a truncated message and a hover tooltip, so the error never fights the suggestions dropdown for the same screen space .
A loadedCompletionsRef caches the in-flight loadCompletions promise so the dropdown doesn't refetch every time it opens; it is reset to null on each focus.
Token Handling — dslFilterConditionFieldUtils.ts#
dslFilterConditionFieldUtils.ts provides three exports used by createDSLFilterCompletionSource :
getDSLFilterCompletionTokenBeforeCursor(textBeforeCursor)— matches the full DSL accessor token at the cursor, including dotted members, quoted subscripts (annotations['name']), integer indexes ([0]), and trailing member-access dots. Getting this right is critical: CodeMirror replaces thefrom…torange when a completion is accepted, so a narrow match that drops an already-typed subscript would duplicate it instead of completing it.validDSLFilterCompletionTokenPattern— theRegExppassed as CodeMirror'svalidFor. If the user keeps typing inside a valid DSL accessor prefix, CodeMirror refilters in place rather than re-invoking every source. Browse results omitvalidForto force a fresh query on the next keystroke.shouldSuppressDSLFilterCompletionsInString({ textBeforeCursor, tokenFrom })— suppresses field-name completions when the cursor is inside a value string literal (e.g.span_kind == 'LL), but not inside a subscript that is part of the token being replaced (e.g.annotations['Human Fee).
createDSLFilterCompletionSource(getOptions) wraps any sync or async option-getter with this logic, making it the standard way to add a new completion vocabulary.
Semantic Convention Completions#
spanFilterSemanticConventionCompletions.ts imports directly from @arizeai/openinference-semantic-conventions :
createOpenInferenceAttributeCompletions()generatesattributes['llm']['provider']-style completions from the package'sSemanticConventionsenum. It excludesnestedOnlySemanticConventionPaths— postfix groups (message.*,document.*,tool_call.*,embedding.text/vector) that only exist inside list items and would produce valid-looking filters that match no spans. Nested list paths (e.g.llm.input_messages,retrieval.documents) are added separately with an explicit[0]index as an editable placeholder.createOpenInferenceAttributeValueCompletionSource()offers enum values (span kinds, LLM providers, MIME types, etc.) when the cursor is inside a quoted right-hand-side literal after a supported comparison. It also handles closing-quote preservation: if the user accepts a completion in the middle of an existing'...', the old suffix is removed rather than left behind.
Annotation and Eval Completions#
annotationCompletions.ts exports createAnnotationMemberCompletions, which takes a list of annotation/eval names fetched from the server and expands each into three completions: accessor['name'].label, .score, and .explanation. These are placed in a createLoadedCompletionSection group (rank 2) so they appear between Suggestions and Fields in the dropdown .
SpanFilterConditionField calls this inside loadCompletions after a Relay query for spanAnnotationNames. ExperimentRunFilterConditionField does the same for annotationSummaries from the compared experiments.
Filter State — SpanFilterConditionContext#
SpanFilterConditionContext is a React context providing:
filterCondition/setFilterCondition— replaces the current expression.appendFilterCondition— appends with" and "when a condition already exists; all mutations usestartTransitionfor non-blocking renders.
Consumers (e.g. the trace table, individual span rows) call appendFilterCondition to add a condition without clearing the existing one .
Adding a New Filter Surface#
To apply the DSL filter to a new page (sessions, datasets, etc.) :
- Define
completions: Completion[]for the DSL vocabulary. - Define
snippets: DSLFilterSnippet[]for example expressions. - Provide a
loadCompletionsfunction for any server-side names. - Inject extra
completionSourcesfor value completions if needed. - Implement
validateConditionusing the page's own GraphQL query. - Render
<DSLFilterConditionField>with these props.
No changes to the base component are required — the DSL is fully caller-defined.