What is a target
A target is one oftraces, threads or llm-spans. It sets what one judged row is and which text the judge reads for it. Pass it as --target on the CLI or target on the API; the default is traces.
A conversation is one row when its traces share a
gen_ai.conversation.id. A project that does not set one has no rows under threads, so ask for traces instead.
The time window
--last takes a number and a unit, 30m, 24h, 7d or 2w, and defaults to 7d. --start and --end take an ISO 8601 timestamp or epoch milliseconds. The window is written into the statement as two bound instants, start_at and end_at, so a run that pages through its rows several times reads one fixed selection.
Narrowing the rows with a trace filter
--filter takes the same syntax as the Trace Explorer’s search bar, with AND, OR, NOT, parentheses, * wildcards on model, and ranges such as cost:>0.01 and duration:[100 TO 500]. Under threads a conversation is judged when any of its traces matches; under llm-spans a call is judged when its trace matches.
Fields that read spans, evaluations, events, annotations or scenario runs are refused by name, with the list above in the refusal, because dropping the condition would charge you for rows you meant to leave out. Ask for those with a statement of your own instead.
The statement a target writes
Every run is one LangWatchQL statement. A target plus your questions expands into one, the run stores it, andrun prints it under Statement: with the two window values under Parameters:. Each question becomes one column, named after the question.
traces, with one yes or no question:
threads, one row per conversation, addressed by the conversation’s last trace:
llm-spans, one row per model call:
--filter on traces goes into that WHERE. On threads and llm-spans it goes into a subquery over analytics.traces with the same window, because the trace’s attributes are not columns of the conversation or span views.
What is the token budget per row
Undertraces the digest is cut to 8,000 tokens. Under threads and llm-spans the text goes to the judge whole, up to the judge’s own ceiling of about 31,000 tokens once your questions are counted in. A long conversation therefore costs more than a short one, and the estimate measures your rows as they are.
When a conversation is over the ceiling, you still get a verdict: LangWatch keeps the opening and closing turns and puts a marker in the middle with the number of turns left out, so a question about how the conversation ended still reads the ending. To choose the budget yourself, write the statement with conversation_bounded, which cuts the same way at the size you name:
Writing your own statement
Take the statement a run printed, edit it, and send it back with--sql.
Run it before you judge it
Run the statement throughlangwatch query first to see the rows before you pay to judge them. It needs the two window values the run printed:
What a statement needs
A statement is a run when:- it projects
TraceIdand at least one eval function column - it projects
SpanIdtoo when it has one row per span, so you get one verdict per span - it has an
ORDER BYwhen you use aLIMIT, so every pass over the rows reads the same set
--sql or --target, not both. Use --param only with your own statement.
Text functions
The text to judge is one of these functions over the row’s key:Eval functions
The question is one of these functions over that text:
The same functions work in a plain
langwatch query, which judges the rows the query returns and charges for them the same way. A run is how you take one past the query’s page size, up to the row cap.