Skip to content

Commit 7e18d5a

Browse files
committed
fix: select thread id for deduped trace listing
1 parent df35d11 commit 7e18d5a

3 files changed

Lines changed: 66 additions & 16 deletions

File tree

internal/cmd/helpers.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ func buildRunSelect(includeIO, includeFeedback bool) []langsmith.RunQueryParamsS
117117
return nil
118118
}
119119

120+
return buildRunSelectFields(includeIO, includeFeedback)
121+
}
122+
123+
func buildRunSelectWithThreadID(includeIO, includeFeedback bool) []langsmith.RunQueryParamsSelect {
124+
return appendRunSelect(buildRunSelectFields(includeIO, includeFeedback), langsmith.RunQueryParamsSelectThreadID)
125+
}
126+
127+
func buildRunSelectFields(includeIO, includeFeedback bool) []langsmith.RunQueryParamsSelect {
120128
fields := []langsmith.RunQueryParamsSelect{
121129
// Base fields
122130
langsmith.RunQueryParamsSelectID,
@@ -155,6 +163,15 @@ func buildRunSelect(includeIO, includeFeedback bool) []langsmith.RunQueryParamsS
155163
return fields
156164
}
157165

166+
func appendRunSelect(fields []langsmith.RunQueryParamsSelect, field langsmith.RunQueryParamsSelect) []langsmith.RunQueryParamsSelect {
167+
for _, existing := range fields {
168+
if existing == field {
169+
return fields
170+
}
171+
}
172+
return append(fields, field)
173+
}
174+
158175
// extractRunsToMaps extracts a slice of runs to maps.
159176
func extractRunsToMaps(runs []langsmith.RunSchema, includeMetadata, includeIO, includeFeedback bool) []map[string]any {
160177
result := make([]map[string]any, 0, len(runs))

internal/cmd/trace.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,9 @@ func newTraceListCmd() *cobra.Command {
7474
}
7575

7676
params := BuildRunQueryParams(&ff, true, ff.Limit)
77-
if sel := buildRunSelect(includeIO, includeFeedback); sel != nil {
78-
// When de-duping by thread, thread_id must be selected so it
79-
// is present on the returned runs.
80-
if onePerThread {
81-
sel = append(sel, langsmith.RunQueryParamsSelectThreadID)
82-
}
77+
if onePerThread {
78+
params.Select = langsmith.F(buildRunSelectWithThreadID(includeIO, includeFeedback))
79+
} else if sel := buildRunSelect(includeIO, includeFeedback); sel != nil {
8380
params.Select = langsmith.F(sel)
8481
}
8582
var runs []langsmith.RunSchema

internal/cmd/trace_test.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,26 @@ func traceRunWithTokens(id, traceID, threadID, startTime string, totalTokens int
171171
return run
172172
}
173173

174+
func requireSelectContains(t *testing.T, body map[string]any, expected ...string) {
175+
t.Helper()
176+
177+
selects, ok := body["select"].([]any)
178+
if !ok {
179+
t.Fatalf("expected select array in request, got %#v", body["select"])
180+
}
181+
seen := make(map[string]bool, len(selects))
182+
for _, sel := range selects {
183+
if s, ok := sel.(string); ok {
184+
seen[s] = true
185+
}
186+
}
187+
for _, field := range expected {
188+
if !seen[field] {
189+
t.Fatalf("expected select to include %q, got %#v", field, selects)
190+
}
191+
}
192+
}
193+
174194
func TestTraceListCmd_OnePerThreadFlagDefault(t *testing.T) {
175195
cmd := newTraceListCmd()
176196
f := cmd.Flags().Lookup("one-per-thread")
@@ -291,6 +311,31 @@ func TestTraceListCmd_OnePerThreadAppliesMinTokensBeforeDedupe(t *testing.T) {
291311
}
292312
}
293313

314+
func TestTraceListCmd_OnePerThreadSelectsThreadIDWithoutIncludeFlags(t *testing.T) {
315+
var requests []map[string]any
316+
ts := newTraceListPagingServer(t, []traceListPage{{
317+
runs: []map[string]any{
318+
traceRun("run-a", "trace-a", "thread-a", "2026-01-01T00:00:00Z"),
319+
},
320+
}}, &requests)
321+
322+
_ = runTraceListJSON(t, ts.URL, "--one-per-thread", "--limit", "1")
323+
324+
if len(requests) != 1 {
325+
t.Fatalf("expected 1 run query request, got %d", len(requests))
326+
}
327+
requireSelectContains(t, requests[0],
328+
"id",
329+
"trace_id",
330+
"name",
331+
"run_type",
332+
"parent_run_id",
333+
"start_time",
334+
"end_time",
335+
"thread_id",
336+
)
337+
}
338+
294339
func TestTraceListCmd_OnePerThreadIncludeIOSelectsThreadID(t *testing.T) {
295340
var requests []map[string]any
296341
ts := newTraceListPagingServer(t, []traceListPage{{
@@ -304,16 +349,7 @@ func TestTraceListCmd_OnePerThreadIncludeIOSelectsThreadID(t *testing.T) {
304349
if len(requests) != 1 {
305350
t.Fatalf("expected 1 run query request, got %d", len(requests))
306351
}
307-
selects, ok := requests[0]["select"].([]any)
308-
if !ok {
309-
t.Fatalf("expected select array in request, got %#v", requests[0]["select"])
310-
}
311-
for _, sel := range selects {
312-
if sel == "thread_id" {
313-
return
314-
}
315-
}
316-
t.Fatalf("expected select to include thread_id, got %#v", selects)
352+
requireSelectContains(t, requests[0], "thread_id", "inputs", "outputs", "error")
317353
}
318354

319355
// ==================== trace get flags ====================

0 commit comments

Comments
 (0)