Where does the statement come from
Every run executes one LangWatchQL statement. Send a target and your questions instead of a statement and LangWatch writes one for you, stores it on the run, andrun prints it under a Statement heading.
Take that statement when you need more than the shorthand covers. Edit it and
submit it back with --sql, or run it through
the query endpoint to see the rows before you
judge them.
The statement takes two parameters, start_at and end_at, so supply both
when you run it again:
run prints these two values beside the statement. Change them to move the
window.
What each target expands into
Every statement projectsTraceId, whatever other key columns the target has,
and one column per question. The window is bound as two instants rather than
written as a relative expression, because a run executes its statement several
times and a moving window would page a selection that was never counted.
traces
threads
traces instead.
conversation carries no budget of its own, so an ordinary conversation reaches
the judge whole. See The token budget for what happens to
one that does not fit, and for how to put a budget back.
llm-spans
The token budget
traces and llm-spans are written with a budget of 8,000 tokens, which is
the default the function catalog documents. A typical product trace renders to
well under it; a coding session renders to many times it.
threads is written without one. A conversation is the thing people most often
want read end to end, and cutting it at 8,000 tokens to save a fraction of a
cent is the wrong trade, so the only ceiling it meets is the judge’s own state:
about 31,000 tokens once your questions are in.
Expect a long conversation to cost more than a bounded one would, up to roughly
four times on the longest threads. Read the estimate before the run: it prices
the rows as they actually are.
A conversation past that ceiling is still judged. It is cut to fit and the row
comes back marked truncated, and the cut keeps the opening and the close and
writes a marker naming how many turns went from the middle, so a question about
how a conversation ended reads the ending.
To set the budget yourself rather than take the judge’s, write the statement
with conversation_bounded, which cuts the same way at the size you name:
What a shorthand filter can ask for
--filter takes the same syntax as the trace explorer’s search bar. A target
supports the fields of the trace row itself:
AND, OR, NOT and parentheses work as they do in the explorer, and so do
* wildcards on model and the range spellings cost:>0.01 and
duration:[100 TO 500].
Every other field the explorer filters on reaches outside the trace row, and a
shorthand refuses it by name rather than dropping the condition. A dropped
condition would charge you for judging rows you meant to exclude.
You can filter on
status:error, because the error flag is a column of the
trace row. status:ok and status:warning read the evaluation rows instead,
so ask for those with --sql.
Ask any of those with a statement instead, using --sql: LangWatchQL reaches
every table, and the trace filter is only a shortcut for the common case. When
a field is refused you get back the field you wrote and the fields you can use
instead.
Where does the filter go
Overtraces, LangWatch puts your filter in the statement’s own WHERE clause.
Over threads and llm-spans it goes into a subquery on the trace view,
because the trace’s attributes are not columns of the metrics view or the span
view.
You get a conversation when any of its traces matches, and a model call when
its trace matches. The same time bounds apply inside each subquery, so you
never pay for rows outside your window.
Sending a statement instead
A statement needs two things to be a run: it has to projectTraceId, and it
has to project at least one eval function column. Everything else is yours.
sql or target, not both and not neither. Use --param only
with your own statement: a target writes its own parameters, so you have none
left to fill.