Skip to content

Commit 643e48c

Browse files
committed
fix: replay OpenAI web searches inline when not referenced
coder/fantasy#78 now sends a search inline whenever it does not go back as an item reference, not only when store is false. A stored turn after an unstored one, and stored searches with no reasoning before them, now keep their searches; the tests expect the inline item in those cases.
1 parent 8ca9ca6 commit 643e48c

4 files changed

Lines changed: 22 additions & 35 deletions

File tree

‎coderd/exp_chats_test.go‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10760,10 +10760,8 @@ func TestChatOpenAIResponsesWebSearch(t *testing.T) {
1076010760
wantError bool
1076110761
wantFound []string
1076210762
// wantReplay is the web_search_call input item the next turn
10763-
// sends, or empty when it sends none. Stored searches replay
10764-
// only as item references paired with reasoning, which this
10765-
// model does not produce, so cases that leave store unset send
10766-
// none.
10763+
// sends. gpt-4o produces no reasoning, so stored searches cannot
10764+
// go back as item references and replay inline too.
1076710765
wantReplay string
1076810766
}{
1076910767
{
@@ -10775,6 +10773,7 @@ func TestChatOpenAIResponsesWebSearch(t *testing.T) {
1077510773
wantArgs: `{"type":"search","queries":["tokyo population","tokyo census 2025"]}`,
1077610774
wantResult: `{}`,
1077710775
wantFound: found,
10776+
wantReplay: `{"type":"web_search_call","status":"completed","action":{"type":"search","queries":["tokyo population","tokyo census 2025"],"query":"tokyo population"}}`,
1077810777
},
1077910778
{
1078010779
name: "Failed",
@@ -10785,6 +10784,7 @@ func TestChatOpenAIResponsesWebSearch(t *testing.T) {
1078510784
wantArgs: `{"type":"search","queries":["tokyo population"]}`,
1078610785
wantResult: `{"error":"web search failed"}`,
1078710786
wantError: true,
10787+
wantReplay: `{"type":"web_search_call","status":"failed","action":{"type":"search","queries":["tokyo population"]}}`,
1078810788
},
1078910789
{
1079010790
name: "UnstoredSearch",
@@ -11062,14 +11062,8 @@ func TestChatOpenAIResponsesWebSearch(t *testing.T) {
1106211062
inputItems = append(inputItems, item.Type)
1106311063
}
1106411064
}
11065-
wantInputItems := []string{"user", "assistant", "user"}
11066-
if tt.wantReplay != "" {
11067-
wantInputItems = []string{"user", "web_search_call", "assistant", "user"}
11068-
}
11069-
require.Equal(t, wantInputItems, inputItems)
11070-
if tt.wantReplay != "" {
11071-
require.JSONEq(t, tt.wantReplay, string(replayed))
11072-
}
11065+
require.Equal(t, []string{"user", "web_search_call", "assistant", "user"}, inputItems)
11066+
require.JSONEq(t, tt.wantReplay, string(replayed))
1107311067
})
1107411068
}
1107511069
}

‎coderd/x/chatd/integration_responses_test.go‎

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,16 @@ import (
2929
func TestOpenAIResponsesNoStaleWebSearchReplay(t *testing.T) {
3030
t.Parallel()
3131

32+
// The follow-up sends the search inline right after the reasoning
33+
// item in both cases: the source response was unstored, so OpenAI
34+
// cannot resolve a reference to it.
35+
const wantSearchReplay = `{"type":"web_search_call","status":"completed","action":{"type":"search","query":"coder changelog"}}`
3236
for _, tc := range []struct {
3337
name string
3438
destinationStore bool
3539
summary string
36-
// wantSearchReplay is the inline web_search_call the follow-up
37-
// sends right after the reasoning item, or empty when it sends
38-
// none.
39-
wantSearchReplay string
4040
}{
41-
{
42-
name: "stateless",
43-
summary: "checked provider-side search state",
44-
wantSearchReplay: `{"type":"web_search_call","status":"completed","action":{"type":"search","query":"coder changelog"}}`,
45-
},
41+
{name: "stateless", summary: "checked provider-side search state"},
4642
{name: "switch to stored", destinationStore: true},
4743
} {
4844
t.Run(tc.name, func(t *testing.T) {
@@ -133,14 +129,10 @@ func TestOpenAIResponsesNoStaleWebSearchReplay(t *testing.T) {
133129
requireNoResponsesProviderItemReplay(t, followup.Prompt, webSearchID)
134130
reasoningIndex := requireInlineReasoningItem(t, followup.Prompt, reasoningID, "encrypted-no-stale",
135131
tc.summary)
136-
if tc.wantSearchReplay != "" {
137-
require.Greater(t, len(followup.Prompt), reasoningIndex+1, "no item after the reasoning item")
138-
replayed, err := json.Marshal(followup.Prompt[reasoningIndex+1])
139-
require.NoError(t, err)
140-
require.JSONEq(t, tc.wantSearchReplay, string(replayed))
141-
} else {
142-
require.NotContains(t, promptItemTypes(followup.Prompt), "web_search_call")
143-
}
132+
require.Greater(t, len(followup.Prompt), reasoningIndex+1, "no item after the reasoning item")
133+
replayed, err := json.Marshal(followup.Prompt[reasoningIndex+1])
134+
require.NoError(t, err)
135+
require.JSONEq(t, wantSearchReplay, string(replayed))
144136
})
145137
}
146138
}

‎go.mod‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,11 @@ replace github.com/spf13/afero => github.com/aslilac/afero v0.0.0-20250403163713
124124
// part's bold title renders on its own line. Upstream as
125125
// charmbracelet/fantasy#436.
126126
// 17) coder/fantasy#78 (unmerged), replay OpenAI web searches inline
127-
// from their result metadata when store is false. Pinned to the PR
128-
// branch.
129-
// See: https://github.com/coder/fantasy/commits/a4106840afd8
130-
replace charm.land/fantasy => github.com/coder/fantasy v0.0.0-20261027162253-a4106840afd8
127+
// from their result metadata unless they go back as an item
128+
// reference, which needs a stored request, a stored source response
129+
// and a reasoning item before the search. Pinned to the PR branch.
130+
// See: https://github.com/coder/fantasy/commits/50bf6c5f5382
131+
replace charm.land/fantasy => github.com/coder/fantasy v0.0.0-20261027173522-50bf6c5f5382
131132

132133
// coder/coder uses a fork of charmbracelet's fork of the Anthropic Go SDK
133134
// with performance improvements and Bedrock header cleanup.

‎go.sum‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,8 @@ github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 h1:SBN/DA63+ZHwu
323323
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41/go.mod h1:I9ULxr64UaOSUv7hcb3nX4kowodJCVS7vt7VVJk/kW4=
324324
github.com/coder/clistat v1.2.1 h1:P9/10njXMyj5cWzIU5wkRsSy5LVQH49+tcGMsAgWX0w=
325325
github.com/coder/clistat v1.2.1/go.mod h1:m7SC0uj88eEERgvF8Kn6+w6XF21BeSr+15f7GoLAw0A=
326-
github.com/coder/fantasy v0.0.0-20261027162253-a4106840afd8 h1:1XeEV0QdZT12WtLKvRE4D98ynb6mFsO2u5WBwKCe8B0=
327-
github.com/coder/fantasy v0.0.0-20261027162253-a4106840afd8/go.mod h1:qTrE+p9h8zW+x0f0mU8ih2VdmYHX4ULCaWImvuMHJMc=
326+
github.com/coder/fantasy v0.0.0-20261027173522-50bf6c5f5382 h1:YkhOVfjxf5NABezmYEFI929Ng0aunoe8erJvcUPCwB8=
327+
github.com/coder/fantasy v0.0.0-20261027173522-50bf6c5f5382/go.mod h1:qTrE+p9h8zW+x0f0mU8ih2VdmYHX4ULCaWImvuMHJMc=
328328
github.com/coder/flog v1.1.0 h1:kbAes1ai8fIS5OeV+QAnKBQE22ty1jRF/mcAwHpLBa4=
329329
github.com/coder/flog v1.1.0/go.mod h1:UQlQvrkJBvnRGo69Le8E24Tcl5SJleAAR7gYEHzAmdQ=
330330
github.com/coder/go-httpstat v0.0.0-20230801153223-321c88088322 h1:m0lPZjlQ7vdVpRBPKfYIFlmgevoTkBxB10wv6l2gOaU=

0 commit comments

Comments
 (0)